悲观锁(Pessimistic Locking)和乐观锁(Optimistic Locking)是在并发控制中常用的两种策略,用于处理多个线程或进程同时访问共享资源的情况。
悲观锁是一种悲观的策略,它假设在并发情况下会发生冲突和竞争,因此在访问共享资源之前先加锁,确保其他线程或进程无法同时访问。悲观锁的作用是保证数据的一致性和可靠性,但也会带来一定的性能损耗,因为其他线程或进程需要等待锁的释放才能继续执行。
乐观锁是一种乐观的策略,它相信在并发情况下不会发生冲突和竞争,因此不会加锁,而是在更新数据时进行检查,例如使用版本号或时间戳来标识数据的版本。如果在更新数据的过程中发现数据已经被其他线程或进程修改,则表示发生了冲突,需要进行回滚或重新尝试。乐观锁的作用是提高并发性能,减少锁的开销,但也会增加代码的复杂性,因为需要处理冲突和回滚的情况。
悲观锁适用于对共享资源的访问频率较高,且数据冲突的概率较高的情况,例如数据库的更新操作。乐观锁适用于对共享资源的访问频率较低,且数据冲突的概率较低的情况,例如缓存的更新操作。
在实际应用中,可以根据具体的业务场景和性能要求选择适合的并发控制策略,或者结合使用悲观锁和乐观锁的特点,例如先使用乐观锁检查数据的版本,如果发现冲突,则切换为悲观锁进行加锁和更新操作。
悲观锁的实现在Java中可以通过synchronized关键字或ReentrantLock类来实现。以下是使用synchronized关键字实现悲观锁的示例代码:
public class PessimisticLockExample {
private int count = 0;
public synchronized void increment() {
// 加锁保证其他线程无法同时访问
count++;
}
public static void main(String[] args) {
PessimisticLockExample example = new PessimisticLockExample();
// 创建多个线程并发执行increment方法
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
// 等待两个线程执行完成
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + example.count);
}
}
在上述代码中,使用synchronized关键字修饰了increment
方法,这样在执行count++
操作时会自动加锁,确保其他线程无法同时访问count
变量。通过创建两个线程并发执行increment
方法,可以测试悲观锁的效果。
需要注意的是,synchronized关键字是可重入锁,也就是说同一个线程可以多次获取同一个锁。这样可以避免同一个线程对共享资源的多次访问出现冲突。
除了synchronized关键字之外,还可以使用Java.util.concurrent包中的ReentrantLock类来实现悲观锁。使用ReentrantLock类可以更灵活地控制锁的获取和释放,可以通过lock()
和unlock()
方法手动控制加锁和解锁的时机。使用ReentrantLock类可以实现更复杂的锁控制策略,例如可重入、公平锁等。
乐观锁的实现在Java中可以通过使用版本号或时间戳来实现。以下是使用版本号实现乐观锁的示例代码:
public class OptimisticLockExample {
private int count = 0;
private int version = 0;
public void increment() {
int currentVersion = version;
// 模拟获取数据库最新版本号或时间戳
int latestVersion = getCurrentVersion();
if (currentVersion == latestVersion) {
// 版本号匹配,执行更新操作
count++;
version++; // 更新版本号
} else {
// 版本号不匹配,表示数据已被其他线程修改,抛出异常或进行相应处理
throw new OptimisticLockException("Data has been modified by other thread.");
}
}
private int getCurrentVersion() {
// 模拟获取数据库最新版本号或时间戳的逻辑
return 1;
}
public static void main(String[] args) {
OptimisticLockExample example = new OptimisticLockExample();
// 创建多个线程并发执行increment方法
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
// 等待两个线程执行完成
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + example.count);
}
}
在上述代码中,通过getCurrentVersion()
方法模拟获取数据库最新版本号或时间戳的逻辑。在increment()
方法中,首先获取当前版本号,然后获取最新版本号,如果两者相等,则执行更新操作并增加版本号;如果不相等,则表示数据已经被其他线程修改,可以抛出异常或进行其他处理。
需要注意的是,在实际应用中,获取最新版本号或时间戳通常需要通过访问数据库或其他共享资源来实现。这里的getCurrentVersion()
方法只是一个示例,具体实现需要根据实际情况来设计。