悲观锁与乐观锁介绍:
悲观锁:
即排他锁,假设冲突总会存在,即每次拿数据的时候都认为别人会修改,所以每次拿数据都会加锁.比如synchronize
乐观锁:
假设每次取拿数据的时候,都没有别人在操作,所以不会上锁.但是在更新的时候会判断下再此期间有没有没人去更新过这个数据.常用的有版本号控制/CAS等等.
乐观锁一般多用于读这种场景,可以提高吞吐量.例如concurrent.atomic包下面的原子变量就是使用了乐观锁的一种基于CAS实现.
需要一提的是,乐观锁适合竞争冲突不频繁的场景,否则性能还不如使用synchronize。
乐观锁实现方式介绍:
Version方式:
给数据加上一个版本号信息,数据被修改时,version值+1.在更新时,读取到的version值和目前的version相等才会更新,否则会一直重试,直到更新成功.
CAS方式:
Compare and swap/set.涉及到三个操作数: 数据值,预期值,拟写入的新值. 当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试.CAS一般情况下是一个自旋操作,即不断的重试。Java concurrent包下面中的atomic类就是基于CAS方式实现的.
JDK8中的AtomicInteger实现分析:
AtomicInteger原子类个人理解它是为了保证i++、i--之类的操作线程安全性而出现的。这些操作在多线程的情况并不是线程安全的,但是使用AtomicInteger可以保证这些操作的安全性。
AtomicInteger在项目中使用的还是蛮多的,所以本文这里尝试分析下类内部的实现机制。先分析下构造函数,再分析下常用的方法。
Ps1: JDK8之后,很多方法都放到unsafe类中去了,这点与JDK7有点不同.
Ps2:CAS通过调用JNI的代码实现,同步并不是阻塞在软件层面,而是阻塞在硬件层面
此处已AtomicInteger类为例,AtomicInteger类有三个属性,分别是:
- unsafe对象, unsafe类提供了硬件级别的原子操作,本类中表现的主要是线程同步机制
- value ,类中所存储的value的值,注意它有volatile修饰,具备可见性.
- valueOffset, 指的就是value这个属性在内存中的偏移量(内存中的地址,而不是值).是通过反射获取到的.
看下其中的一种CAS方法 getAndSet():
Unsafe中代码实现如下:
其他方法就不解析,使用的都是类似的CAS方法。
CAS实现原理底层代码暂时还没有,可以看下参考中的链接,大概意思是基于基于底层硬件实现的。
CAS的缺点:
1.CAS无法避免ABA问题, 从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2.CAS使用的是自旋锁,自旋时间长的话,开销大
3.CAS只能保证一个变量的原值操作,但是从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
参考网页:
https://www.cnblogs.com/pkufork/p/java_unsafe.html (unsafe类的作用)
https://www.cnblogs.com/qjjazry/p/6581568.html(CAS一些概念介绍)
https://juejin.im/post/5a73cbbff265da4e807783f5(CAS底层实现原理)