什么是乐观锁、悲观锁?
乐观锁和悲观锁都是在并发控制常使用的策略。
乐观锁:线程对共享数据进行操作时,都会假设地认为其他线程不会用到该数据,不会对数据进行加锁,而是在线程更新数据时检查是否与其他线程的该数据发生冲突,如果有冲突,就会回退并重试操作,直到成功为止。
悲观锁:线程对共享数据进行操作时,都会假设地认为其他线程会用到该数据,会对数据进行加锁,在获取资源时,就直接上锁,确保其他线程无法修改资源。只有当本线程操作完成并更新完数据时,才释放锁的资源,让其他线程去进行操作。
乐观锁、悲观锁是怎么实现的?
在数据库中,乐观锁通常通过版本号Version或时间戳Timestamp来实现。
在数据库中,悲观锁通常通过数据库的锁机制来实现,比如行级锁、表级锁。
在Java中,乐观锁通常使用CAS(Compare and Swap)操作来实现,如AtomicInteger类和AtomicReference类。示例:
public class OptLock {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
while (!counter.compareAndSet(oldValue, newValue)){
oldValue = counter.get();
newValue = oldValue + 1;
}
}
}
在Java中,悲观锁通常使用synchronized关键字和ReentrantLock类。示例:
public class PesLock {
private int counter = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
}
乐观锁、悲观锁的优缺点
乐观锁优点:不需要加锁,所以线程读取和修改数据时很快,不会担心死锁。
缺点:如果CAS自旋一直失败,很大地增加了CPU的负担。
悲观锁优点:加锁,只允许一个线程操作当前数据,操作完成后释放锁,CPU利用率高。
缺点:可能会产生死锁;需要等待其他线程释放锁,才能获取所需要的数据,等待时间可能会很长,线程切换较慢。
乐观锁、悲观锁的适用场景
乐观锁:1.读操作远远大于写操作数量时,2.并发冲突不严重时。
悲观锁:1.写操作远远大于读操作数量时,2.并发冲突严重时。