ReentrantLock 的实现原理
在 Lock 中,用到了一个同步队列 AQS,全称AbstractQueuedSynchronizer,它是一个同步工具也是 Lock 用来实现线程同步的核心组件。
AQS 的功能分为两种:独占和共享
独占锁,每次只能有一个线程持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
共 享 锁 , 允 许 多 个 线 程 同 时 获 取 锁 , 并 发 访 问 共 享 资 源 , 比 如ReentrantReadWriteLock。
AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。每个 Node 其实是封装当前线程,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
AQS释放锁以及添加线程对于队列的变化
添加节点
- 新的线程封装成 Node 节点追加到同步队列中,设置 prev 节点以及修改当前节
点的前置节点的 next 节点指向自己 - 通过 CAS 讲 tail 重新指向新的尾部节点
释放锁
head 节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下:
4. 修改 head 节点指向下一个获得锁的节点
5. 新的获得锁的节点,将 prev 的指针指向 null
设置 head 节点不需要用 CAS,原因是设置 head 节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要 CAS 保证,只需要把 head 节点设置为原首节点的后继节点,并且断开原 head 节点的 next 引用即可。
源码
lock
public void lock() {
sync.lock();
}
ReentrantLock 中sync 实际上是一个抽象的静态内部类,它继承了 AQS 来实现重入锁的逻辑.
以非公平锁为例,来看看 lock 中的实现
- 非公平锁和公平锁最大的区别在于,在非公平锁中,不管有没有线程排队, cas 抢占一下
- CAS 成功,就表示成功获得了锁
- CAS 失败,调用 acquire(1)走锁竞争逻辑
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
通过 cas 乐观锁的方式来做比较并替换,这段代码的意思是,如果当前内存中的state 的值和预期值 expect 相等,则替换为 update。更新成功返回 true,否则返回 false.这个操作是原子的,不会出现线程安全问题
/**
* The synchronization state.
*/
private volatile int state;
1.state=0表示无锁。
2.state>0表示获得锁的次数,重入的次数。
AQS .accquire
这个方法的主要逻辑是
- 通过 tryAcquire 尝试获取独占锁,如果