ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。而且它具有比synchronized更多的特性,比如它支持手动加锁与解锁,支持加锁的公平性。
1 使用ReentrantLock进行同步
2 ReentrantLock lock = new ReentrantLock(false);//false为非公平锁,
true为公平锁
3 lock.lock() //加锁
4 lock.unlock() //解锁
ReentrantLock如何实现synchronized不具备的公平与非公平性呢?
在ReentrantLock内部定义了一个Sync的内部类,该类继承AbstractQueuedSynchronized,对该抽象类的部分方法做了实现;并且还定义了两个子类:
1、FairSync 公平锁的实现
2、NonfairSync 非公平锁的实现
这两个类都继承自Sync,也就是间接继承了AbstractQueuedSynchronized,所以这一个ReentrantLock同时具备公平与非公平特性。
Lock三大核心原理
自旋、 locksuport 、CAS
多个线程去竞争锁 通过cas保证锁的互斥性, cas获取锁失败就会自旋 自旋一定次数后进入由node节点构成的队列 节点会记录thread(线程引用) waitestatus(节点生命状态) 以及前后节点的引用, 线程进入队列中并不会立即调用locksuport.park方法进行阻塞 而是在阻塞之前会再去尝试获取锁 如果能获取到锁 节点出队 并且把head往后挪一个节点 如果获取锁失败就先将waitstate改为-1在进行阻塞 ,当t0线程(获取锁的线程)执行了unlock释放锁后判断head节点的waitstate是否!=0 如果!=0成立 先将waitstate=-1置为0, 在调用方法唤醒队列中的head中的线程去抢锁 ,如果是公平锁 那么会抢锁成功 如果是非公平情况下 那么可能会抢锁失败 如果抢锁失败被其他线程获取了锁 那么会再次被阻塞 head节点waitstate状态在改为-1 。
Waitestate节点生命状态(信号量)
Singal = -1
Cancelled = 1
Condition = -2
Propagate = -3
0 初始状态
通过cas保证锁的互斥性 cas获取锁失败就会自旋 自旋一定次数后调用locksuport.park方法进行阻塞 最后当锁释放的时候调用locksuport.unpark方法唤醒阻塞队列中(双向列表)的线程
Cas原理:
线程A B对变量y=0做修改为1 首先线程A B 先将变量读到工作内存 比如线程A先将变量拿到和主内存做比较,并且变量值相等(工作内存和主内存值相等) 那么就进行修改(y=1), 此时B线程再拿到读取到的y=0去比较与主内存值不相等(B的工作内存中的y=0和主内存中的y=1不相等了) 就不会修改
CAS存在的问题
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
- ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 - 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
- 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作
可重入
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决一下两个问题:
1、线程再次进入:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
2、锁的 最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减去,当计数为0时表示锁已经成功释放。
成功获取锁的线程再次获取锁,只是增加了同步状态值。