目录
1.引言
1.1 类比synchronized
2.使用
2.1 同步
2.2 通信协同
3.实现
2.1 类图
2.2 核心方法
2.2.1 构造
2.2.2 lock
2.3 AQS
2.3.1 字段分析
2.3.2 lock()方法
2.3.3 release(int)
在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加的灵活。
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。虽然保证了实例变量的线程安全性,但效率却是非常低下的
synchronized | ReentrantLock | ||
---|---|---|---|
相同点 | 可重入 比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的 |
||
不同点 | 实现 | JVM实现 | JDK API (java.util.concurrent包) |
使用 | 使用简单 通信机制配合 synchronized与wait()和notify()/notifyAll() |
使用略复杂 需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成 通信配合 Condition await和signal |
|
功能 | 功能简单单一 不可中断 仅支持非公平锁 |
高级功能 等待可中断; lock.lockInterruptibly()等待的线程可选择放弃等待 可实现公平锁;(公平锁就是先等待的线程先获得锁,默认情况是非公平, 可实现选择性通知(锁可以绑定多个条件)ReentrantLock与Condition实例可以实现“选择性通知” |
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock(); // 3个线程竞争的锁
// 启动线程1
new Thread(()->{
try {
lock.lock();
Thread.sleep(20000); // 休眠20s后释放锁(sleep锁不释放)
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread1").start();
Thread.sleep(5000); // 主线程休眠5s
// 启动线程2
new Thread(()->{
try {
lock.lock();
Thread.sleep(30000); // 休眠30s后释放锁(sleep锁不释放)
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread2").start();
Thread.sleep(5000); // 主线程休眠5s
// 启动线程3
new Thread(()->{
try {
lock.lock();
Thread.sleep(30000); // 休眠30s后释放锁(sleep锁不释放)
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread3").start();
}
配合try finally,可以搭配多个condition
public class ConditionUseCase {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await(); // 条件不满足时等待
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal(); // 条件满足时唤醒
} finally {
lock.unlock();
}
}
}
从类图可以看出底层基于AQS实现,ReentrantLock的lock等方法,委托给其依赖sync的lock方法
默认无参构造使用非公平锁实现,可以传入参数选择使用公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
依赖sync的实现,常用非公平锁的实现,如下图Sync继承自AQS(AbstractQueuedSynchronizer, 队列同步器),先利用CAS(state, 0, 1)操作(类似于synchronized偏向锁优化),下一节会详细介绍AQS实现
AQS核心思想是,如果被请求的共享资源空闲,则将当前线程设置为有效的工作线程,并且将共享资源设置为锁定状态state。
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,将暂时获取不到锁的线程加入到队列中。
![]() |
等待队列节点类Node,等待队列是“ CLH”(Craig,Landin和 Hagersten)锁定队列的变体,CLH锁通常用于自旋锁。 每个节点中的“状态waitStatus”字段跟踪线程是否应阻塞 |
头是哨兵节点 |
lock方法的时序如下:
// NonfairSync 利用CAS synchronized偏向锁类似
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread()); // 对应于偏向锁
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
lock | acquire |
![]() |
![]() |
方法 | 解析 |
---|---|
1.addWaiter Node node = new Node(Thread.currentThread(), mode); compareAndSetTail(pred, node) |
当前线程创建新的节点,插入到队尾 |
2.acquireQueued |
排队获取锁, for循环自旋获取,如果阻塞了,循环也暂停,如果不阻塞继续获取,获取到了return
仅当前驱节点是哨兵头,才去获取锁CAS 此处获取不成功,由于锁被线程1占有 shouldParkAfterFailedAcquire&&parkAndCheckInterrupt 设前驱为signal , 并parkAndCheckInterrupt阻塞当前线程(重量级) 此时线程的执行暂停了 |
3.selfInterrupt |
中断当前线程 |
以上一节中3个线程竞争锁的代码为例子,
线程1在lock时,锁空闲,CAS可以成功,并将锁owner设为线程1
线程2在lock时,锁已经被线程1占有,acquire(1)抢占锁,
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock unlock委托给AQS的release(1)
public void unlock() {//ReentrantLock
sync.release(1);
}
public final boolean release(int arg) {//AQS
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
方法 | 解析 |
---|---|
1.线程1 tryRelease |
将exclusiveOwnerThread置空,并修改state (由于只有一个线程能进入该代码,无需并发控制) |
2.线程1 unparkSuccessor private void unparkSuccessor(Node node) { |
找到第一个未中断取消的中继节点thread2,将其唤醒uppark |
3.线程2继续执行acquireQueued的for循环 final boolean acquireQueued(final Node node, int arg) { |
此时线程2 tryAcquire可以获得锁,竞争锁成功return setHead(node);设为头节点 |