基本概念
ReentrantLock是自JDK1.5开始引入的一种排他锁,它提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量。我们看下ReentrantLock如何使用的,代码如下:
public class ReentrantLockDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 申请锁
lock.lock();
try {
// 在此对共享数据进行访问
// ...
}catch (Exception e){
e.printStackTrace();
}finally {
// 总是在finally块中释放锁,以避免锁泄露
lock.unlock();
}
}
}
从上述代码可以看出,使用还是挺简单的,这边需要注意一点,就是释放锁最好放在finally块中,以免异常后导致锁没释放成功,造成锁泄露。
常用方法
方法 | 说明 |
---|---|
lock() | 获取锁 |
lockInterruptibly | 如果当前线程未被中断,则获取锁 |
tryLock | 尝试获取锁,没有其它线程竞争则获取到锁并返回true,否则立即返回false |
unlock | 释放锁 |
newCondition | 返回绑定到此Lock实例的新Condition实例 |
CAS
CAS全称是Compare and Swap,即比较并交换,是通过原子指令来实现多线程的同步功能。它修改数据前先从内存中读取出实际值,把它当成预期的值,当真正去修改的时候,会比较此时内存中的实际值跟刚才读取的预期值是否一致,若一致则可以修改成功,若不一致,则修改失败(说明在修改期间被其它线程修改了)。
下图展示了操作期间没有其它线程操作同个内存值:
下图展示了操作期间有其它线程操作同个内存值:
AQS
AQS(AbstractQueuedSynchronizer)即队列同步器,它是构建锁或者其他同步组件的基础框架,它是JUC并发包中的核心基础组件。AQS主要包括如下几个部分:
- 状态变量state:AQS通过一个整数值来标识锁有没有被占用。
- 同步队列:AQS内部维护了一个链表结构的同步队列,当线程竞争锁失败后,会被放入同步队列中。
- Condition队列:这个队列主要用来实现等待/通知(类似synchronized的wait/notify)。
同步队列运行原理
- 当只有一个线程执行时,线程被包装成node,获取到锁并插入链表头部进行执行。
- 多个线程同时竞争锁时,未获取到锁,则被包装成node,并依次插入到阻塞队列尾部。
- 当前拥有锁的线程执行结束后释放锁,并唤醒它后面的一个线程,被唤醒的线程重新竞争锁,获取后则执行,执行完后类似,也释放锁并唤醒后面的一个线程。
Condition队列运行原理
- 当持有锁的线程,调用Condition.await方法使线程进入等待状态时,它先被插入到对应的Condition队列中(条件队列是单向链表)。
- 插入至Condition队列中后它会释放当前锁,并唤醒阻塞队列的后一个线程,自身从阻塞队列中移除。
- 若调用Condition.signal方法,它会唤醒一个等待在Condition上的线程,被唤醒的线程会被插入到阻塞队列的尾部,并从条件队列中删除。
ReentrantLock内部原理
我们跟踪一下ReentrantLock的源码,进入它lock方法:
// ReentrantLock.lock()
public void lock() {
// 这个sync,是我们new ReentrantLock()时初始化的
sync.lock();
}
// 无参构造函数
public ReentrantLock() {
// 默认初始化一个非公平锁
sync = new NonfairSync();
}
这边我们先介绍一下公平锁和非公平锁。
公平锁
公平锁的意思就是所有的线程按照请求锁的顺序依次执行(例如:一个新线程请求时,发现阻塞队列里面有其它线程排队,那么它直接插入到阻塞队列尾端进行排队)。
当我们创建ReentrantLock对象时,传入一个true参数,则创建的就是公平锁:
ReentrantLock lock = new ReentrantLock(true);
非公平锁
非公平锁就是有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的(例如:一个新线程请求,它不管阻塞列表有没有其它线程在排队,它直接去竞争锁,若竞争成功则就直接执行)。
ReentrantLock新建时,默认就是非公平锁,我们进入非公平锁NonfairSync的代码:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 获取锁方法
final void lock() {
if (compareAndSetState(0, 1))
// 获取到了锁设置一下当前线程是自身
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取锁失败,则进入阻塞队列
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
进入compareAndSetState方法:
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
我们之前说过,AQS是通过一个state整数值(默认是0)来标识锁有没有被占用,这边就是通过CAS的方式,把state从0改为1,若修改成功说明成功获取到锁,若修改失败说明获取锁失败。修改失败进入acquire方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 加入阻塞队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以看出,未竞争到锁之后,就会添加至阻塞队列。这边我们就不往后深究了。