乐观锁和悲观锁:
悲观锁:
一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:
一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作。
适合场景:
悲观锁适合于写操作多的场景,乐观锁适合于读操作多的场景
乐观锁的2中实现方式:(乐观锁是一种思想)
1 版本号机制:
一般是在数据表中加一个数据版本version字段,表示数据被修改的次数,当数据被修改时,version的值+1。当线程A要更新数据时,在读取数据的同时也会读取version的值,在提交更新时,若刚才读取到的version的值和当前数据库中的version的值相等才会更新数据,否则重试更新操作,直到更新成功。
2 cas算法 Compare AndSwap(比较和交换):
一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步,cas算法涉及到3个操作数 需要读写的内存值V,进行比较的值A,拟写入新的值。当且仅当V的值等于A时,cas通过原子方式用新值B新V的值,否则不会执行任何操作,(比较和替换是一个原子操作),一般情况下是一个自旋操作,即不断的重试
乐观锁补充:
1 cas算法造成的问题:(ABA问题):
cas算法实现的一个重要前提是读取内存中某时刻的数据,而在下一时刻比较并替换,那么在这个时间差内会导致数据的变化,比如:线程1从内存位置V中取出数据A,这时另一个线程2也从内存位置V取出数据A,此时线程2将A变成B,然后又将V位置的值变成A。此时线程1进行cas操作,发现内存V的值仍然是A,线程1cas操作成功。虽然cas操作成功了,但是这个过程存在问题,可能会引起数据的不一致。
2 cas算法的缺点:
(1):ABA问题
(2):循环时间长,开销大:自旋cas如果长时间不成功,回给cpu带来非常大的执行开销
(3):只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,可以使用循环cas的方式来保证原子操作,但是对多个共享变量进行cas时,循环cas就无法保证操作的原子性,这时候可以用锁,或者将多个共享变量合并成一个共享变量来操作。
3 CAS和synchronized的使用场景:
(1):对于资源竞争较少(线程冲突较轻)的情况, 使用synchronized同步锁进行线程阻塞和唤醒,以及用户内核态间的切换操作额外消耗cpu资源;而cas基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率小,因此可以获得更高的性能。
(2):对于资源竞争严重(线程冲突严重)的情况,cas的自旋概率较高,从而浪费更多的cpu资源,效率袁低于synchronized