ReentrantLock(可重入锁)
ReentrantLock 感觉上是 Synchronized 的增强版,二者都是可重入锁,Synchronized 的特点是使用简单,一切都交给 JVM 去处理,但是在功能上比较薄弱。ReentrantLock 相比 Synchronized 在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点,首先通过一个简单的例子了解一下它的用法:
1.1 可重入
单线程(同一个线程)可以重复进入,但也要重复退出,如果不重复退出的话,其它线程将无法获取锁,会发生死锁的现象。由于 ReentrantLock是可重入锁,所以可以反复得到相同的一把锁,它有一个于锁相关的获取计数器,如果拥有锁的线程再次得到锁,获取计数器将会加1,然后锁被释放两次才能获得真正的释放锁,具体代码如下:
1.2 可中断
ReentrantLock 对中断是有响应的,普通的 lock.lock()是不能响应中断,lock.lockInterruptibly() 是能够响应中断的,我们模拟一个死锁场景,然后用中断来处理死锁,具体代码如下:
上述代码 有可能会发生死锁,线程1得到锁 lock1,线程2得到锁 lock2,然后彼此又想获得对方的锁,用 jstack 命令查看代码运行情况:
可以使用中断把死锁的线程中断掉,中断后线程正常退出,具体代码如下:
1.3 可限时
超时不能获得锁,就返回false,不会永久等待构成死锁。使用 lock.tryLock(long timeout,TimeUnit unit) 来实现可限时锁,参数为时间和单位,具体代码如下:
1.4 公平锁
ReentrantLock无参默认构造函数构造的是非公平锁,如果想创建公平锁,那么只需要使用如下代码:
一般意义上的锁都是非公平的,不一定先来的线程就能获取锁,后来的线程就后得到锁,不公平锁会产生饥饿现象。公平锁能够保证先来的线程先获取锁,但是公平锁的性能会比非公平锁差很多。
下面介绍一下 ReentrantLock 的实现, ReentrantLock 的实现只要有 3 部分组成:
ReentrantLock 有 3 个静态类来实现该类的功能,结构如下:
NonfairSync:非公平锁类,它的作用是在有线程请求锁时,并不能保证首先请求的线程可以拿到这个锁
FairSync:公平锁类,如果已经有线程持有锁了,那么请求线程会被放入队列中,然后按顺序请求锁
ReentrantLock 的父类 AbstractQueuedSynchronizer 中有一个 state 变量来表示同步状态:
通过 CAS 操作来设置 state 状态来获取锁,如果 state 被设置成 1,则将锁的持有者给当前线程,代码如下:
首先先去 tryAccquire 试试申请锁,因为此时可能其他线程释放了锁,如果还没有申请到锁,就执行 addWaiter ,将自己添加到等待的队列中去,addWaiter 具体代码如下: