乐观锁 VS 悲观锁
悲观锁:认为自己在使用数据的时候一定会有线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被其他线程所修改。(例如:synchronized、lock)
乐观锁:认为自己在使用数据的时候不会有其他线程来修改数据,所以不会添加锁,只是会在更新数据的之前判断有没有被其他线程更新,如果没有,则执行更新,如果有,则采用其他方式(报错或重试等)。
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法。(CAS:Compare and Swap,即比较再交换。)
所以说,悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁适合读操作多的场景,没有锁的操作可以提升性能。
/**
* @description: 乐观锁
* @author: annecheng,2019-07-08 10:33
*/
public class OptimisticLock {
//AtomicInteger 原子操作类
private AtomicInteger atomicInteger = new AtomicInteger();
private void testOptimisticLock() {
atomicInteger.incrementAndGet();
}
}
/**
* @description: 悲观锁
* @author: annecheng,2019-07-08 10:32
*/
public class PessimisticLock {
private synchronized void testMethod(){
System.out.println("操作同步资源");
}
//如果一个线程已经获得了锁,其内部还可以多次申请该锁成功,那么该锁为可重入锁。
private ReentrantLock lock = new ReentrantLock();
public void testPessimisticLock(){
lock.lock();
this.testMethod();
lock.unlock();
}
}
乐观锁的实现:CAS
CAS,Compare And Swap(比较和交换),无锁算法,在不使用锁的情况下,实现多线程之间的变量同步。是一个原子性的操作,AtomicInteger就是一种。
AtomicInteger 源码:
属性:
unsafe: 获取并操作内存的数据。
valueOffset: 存储value在AtomicInteger中的偏移量。
value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的。
AtomicInteger的incrementAndGet方法底层调用unsafe.compareAndSwapInt()方法;
getAndAddInt(),给对象o加上偏移量offest,是否等于v,如果相等,设置成v+delta,如果不相等,一直循环重试。
CAS存在的问题:
1、ABA问题,在操作值的时候检查内存值是否发生变化,但是值可能是从A变到B再变回A,那么CAS是检测不到变化的。所以,应该再加一个版本号进行控制。JDK的解决ABA办法:AtomicStampedReference。
2、循环时间长开销带,CAS操作如果长时间不成功,会导致一直自旋,CPU开销很大。
3、只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。JDK提供了AtomicReference类来保证引用对象之间的原子性。