原文链接:Lock – 04 – ReentrantLock底层实现原理
相关文章:
Lock – 01 – synchronized的基本概念
Lock – 02 – synchronized底层实现原理
Lock – 03 – synchronized的优化
Lock – 04 – ReentrantLock底层实现原理
ReentrantLock (重入锁) 是 Lock 接口最常见的一种实现,它与 synchronized 一样是可重入的,在基本语法上,ReentrantLock 也与 synchronized 很相似,只是代码写法上稍有区别而已
ReentrantLock 相比 synchronized 增加了一些高级功能,主要有以下三项
等待可中断
是指当有锁的线程长期不释放锁的时候,正常等待的线程可以选择放弃等待,改为处理其他事情,
可中断特性对处理执行时间非常长的同步块很有帮助
公平锁
是指多个线程在等待同一个锁时,必须要按照申请锁的时间顺序来依次获得锁
非公平锁不能保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁,synchronized 中的锁就是典型非公平锁
ReentrantLock 默认为非公平锁,可以通过带布尔值的构造函数来使用公平锁,不过使用公平锁后,将会导致 ReentrantLock 的性能急剧下降,会明显影响吞吐量
锁绑定多个条件 (选择性通知)
是指一个 ReentrantLock 对象可以同时绑定多个 Condition 对象
在 synchronized 中,锁对象的 wait() 方法和 notify() / notifyAll() 方法相结合可以实现等待 / 通知机制;在 ReentrantLock 中,则需要借助 Condition 接口和 newCondition() 方法来实现
Condition 是在 JDK5 中引入的,具有很好的灵活性,比如可以实现多路通知功能,及在一个 Lock 对象中创建多个 Condition 实例 (即对象监视器),线程对象可以注册在指定的 Condition 中,从而可以有选择性的进行线程通知,在线程调度上更加灵活。而在 synchronized 中使用 notify() / notifyAll() 方法进行通知时,被通知的线程是有 JVM 选择的。因此用 ReentrantLock 类结合 Condition 实例可以实现"选择性通知",由 Condition 接口默认提供
synchronized 相当于整个 Lock 对象中只有一个 Condition 实例,所有线程都注册在它身上,如果执行 notifyAll() 方法的话,就会通知所有处于等待状态的线程,而 Condition 实例的 signalAll() 方法只会唤醒注册在该 Condition 实例中的所有等待线程
ReentrantLock 位于 Java 并发包 (JUC) 中,实现了 Lock 和 Serializable 接口,其内部有一个实现锁功能的关键成员变量 sync (Sync 类型,是继承于 AbstractQueuedSynchronizer (AQS) 的内部抽象类),Sync 在 ReentrantLock 中有两个子类: NonfairSync 和 FairSync,即非公平锁和公平锁
ReentrantLock 通过 lock() 和 unlock() 两个方法来实现加锁和解锁
lock() 方法获取锁的主要流程 (NonfairSync 和 FairSync 中有不同的具体实现)
首先,ReentrantLock 在 lock()
方法中调用了其成员变量 sync 的 lock()
方法 (这是一个抽象方法)
public void lock() {
sync.lock();
}
接着,在 NonfairSync 和 FairSync 两个 Sync 的子类中,覆盖了父类的 lock() 方法,并都会调用 Sync 父类 AbstractQueuedSynchronizer (AQS) 中的 acquire() 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
然后,调用 tryAcquire(arg)
方法去获取独占锁,若获取失败,则会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
方法将请求获取锁的线程 (当前线程) 加入到等待队列中 (确切说应该是一个包含了当前线程的 Node 节点),直到其他线程释放锁,由请求获取锁的线程获取并返回;如果加入队列失败,则会返回 true,进而调用 selfInterrupt()
方法来中断当前线程
Node.EXCLUSIVE
标记表示当前节点正处于独占模式,与之对应的是 Node.SHARED
共享模式
addWaiter(Node.EXCLUSIVE), arg)
方法用于构造一个 Node 节点 (其构造方法中包含了当前线程),再由 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
方法将该节点加入到等待队列中等待,直到其他线程释放锁,由请求获取锁的线程获取并返回
unlock() 方法释放锁的主要流程
ReentrantLock 在 unlock()
方法中调用了其成员变量 sync 的 release(1)
方法,即调用的是 AQS 的 release(int arg)
方法
public void unlock() {
sync.release(1);
}
AQS 的 release(int arg)
方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
AQS 的 release(int arg)
方法中会先调用 tryRelease(arg)
方法,即调用了 Sync 自身的 tryRelease(int releases)
方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
tryRelease(int releases)
方法中,会先计算当前锁的状态 c
,接着再判断当前线程是否是拥有独占锁的线程,如果不是则抛出 IllegalMonitorStateException 异常,然后再判断锁状态 c
的值,如果为 0,则会将拥有独占锁的线程重置为 null,并返回 true;如果不为 0,则返回 false接着会获取等待队列中头部节点 h
,然后判断该节点 h
是否不为 null 且 waitStatus 标识是否不为 0,两者都为 true 时,则会调用 unparkSuccessor(h)
方法来唤醒该节点去获取独占锁
waitStatus 为 Node 节点状态标识,有以下五个值 (负值表示当前节点处于有效等待状态,而正值表示当前节点已被取消,所以源码中很多地方用 >0 或 <0 来判断当前节点的状态是否正常)
CANCELLED (1)
表示当前节点中的线程已被取消
当线程等待超时或被中断时,会触发变更为此状态,进入该状态的节点不会再发生变化,且节点中的线程永远不会再被阻塞
SIGNAL (-1)
表示后继节点正在等待当前节点的唤醒
当节点释放锁或被取消时,会唤醒其后继节点,使后继节点中的线程得以运行
CONDITION (-2)
表示当前节点等待在 Condition 上
当其他线程调用了 Condition 的 signal() 方法后,该节点会从等待队列转移到同步队列中,等待获取同步锁
PROPAGATE (-3)
表示下一次的共享状态会被无条件的传播下去
在共享模式下,当前节点不仅会唤醒其后继节点,同时也可能会唤醒后继的后继节点
0
NonfairSync 继承自 Sync,并覆盖了 Sync 的 lock() 方法,同时还覆盖了 AbstractQueuedSynchronizer 的 tryAcquire() 方法
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);
}
}
由上可知,NonfairSync 的 lock() 方法中,直接使用了基于 CAS 操作的 compareAndSetState(0, 1)
方法来获取锁,若 CAS 操作成功,则会将当前线程设置为该锁的唯一拥有者
AQS 中维护了一个锁状态字段 state,由 volatile 进行修饰,其中 0
表示未被获取,1
表示已被获取, 大于 1
表示重入数
当调用 compareAndSetState(0, 1)
方法来获取锁时,即尝试将锁状态 state
从 0
变为 1
,若操作成功,则其他线程调用 compareAndSetState(0, 1)
方法时就会失败,直到当前线程释放锁为止
若 CAS 操作失败,则会调用 AQS 的 acquire(1)
方法,即调用了 NonfairSync 自身的 tryAcquire(int acquires)
方法来获取独占锁,若获取失败,则会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
方法将请求获取锁的线程加入到等待队列中等待,直到其他线程释放锁,由请求获取锁的线程获取并返回
tryAcquire(int acquires)
方法中又调用了 Sync 的 nonfairTryAcquire(int acquires)
方法,
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
0
时,会进行 CAS 操作来获取独占锁,若操作成功,则将当前线程设置为该锁的唯一拥有者并返回 true;如果持有锁的线程为当前拥有独占锁的线程,则通过累加状态标识 nextc
来记录重入次数,并返回 true;若都不是,则返回 falseFairSync 继承自 Sync,并覆盖了 Sync 的 lock() 方法,同时还覆盖了 AbstractQueuedSynchronizer 的 tryAcquire() 方法
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
由上可知,FairSync 的 lock() 方法中,仅调用了 AQS 的 acquire(1)
方法,即调用了 FairSync 自身的 tryAcquire(int acquires)
方法来获取独占锁,若获取失败,则会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
方法将请求获取锁的线程加入到等待队列中
tryAcquire(int acquires)
方法中,如果锁状态 (state) 为 0
时,会先通过 hasQueuedPredecessors()
方法来判断等待队列中有没有比当前线程等待时间更长的线程 (即优先级更高),如果没有,则再进行 CAS 操作来获取独占锁,若操作成功,则将当前线程设置为该锁的唯一拥有者并返回 true;如果持有锁的线程为当前拥有独占锁的线程,则通过累加状态标识 nextc
来记录重入次数,并返回 true;若都不是,则返回 false
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
等待可中断
public class LockInterruptiblyTest{
private Lock lock = new ReentrantLock();
public void lockTest() {
String threadName = Thread.currentThread().getName();
try {
System.out.println("线程【" + threadName + "】:我来获取锁了");
// lock.lock();
lock.lockInterruptibly();
System.out.println("线程【" + threadName + "】:我获取到锁了,要准备干活了!");
long start = System.currentTimeMillis();
while (true) {
long end = System.currentTimeMillis();
if ((end - start) > 10000L) {
break;
}
}
System.out.println("线程【" + threadName + "】:好累啊,干了10秒钟的活");
} catch (Exception e) {
System.out.println("线程【" + threadName + "】:啊,我被中断了!");
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("线程【" + threadName + "】:收工回家,我要释放锁了");
}
}
public static void main(String[] args) throws Exception {
LockInterruptiblyTest test = new LockInterruptiblyTest();
Thread thread1 = new Thread(() -> test.lockTest(), "thread1");
Thread thread2 = new Thread(() -> test.lockTest(), "thread2");
System.out.println("主线程:我来启动线程【thread1】了");
thread1.start();
System.out.println("主线程:好累啊,先睡个3秒钟,再去启动线程【thread2】吧");
TimeUnit.SECONDS.sleep(3);
System.out.println("主线程:我来启动线程【thread2】了");
thread2.start();
System.out.println("线程【thread2】:诶呀,获取不到锁,先睡个3秒钟吧,然后再看看情况");
TimeUnit.SECONDS.sleep(3);
System.out.println("线程【thread2】:都睡了3秒钟了,还获取不到锁,不干了,我要中断了!");
thread2.interrupt();
}
}
使用 lockInterruptibly() 方法输出如下
// 主线程:我来启动线程【thread1】了
// 主线程:好累啊,先睡个3秒钟,再去启动线程【thread2】吧
// 线程【thread1】:我来获取锁了
// 线程【thread1】:我获取到锁了,准备干活了!
// 主线程:我来启动线程【thread2】了
// 线程【thread2】:诶呀,获取不到锁,先睡个3秒钟吧,然后再看看情况
// 线程【thread2】:我来获取锁了
// 线程【thread2】:都睡了3秒钟了,还获取不到锁,不干了,我要中断了!
// 线程【thread2】:啊,我被中断了!
// java.lang.InterruptedException
// at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
// at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
// at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
// at com.xj.thread.LockInterruptiblyTest.lockTest(LockInterruptiblyTest.java:21)
// at com.xj.thread.LockInterruptiblyTest.lambda$main$1(LockInterruptiblyTest.java:43)
// at java.lang.Thread.run(Thread.java:748)
// Exception in thread "thread2" java.lang.IllegalMonitorStateException
// at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
// at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
// at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
// at com.xj.thread.LockInterruptiblyTest.lockTest(LockInterruptiblyTest.java:35)
// at com.xj.thread.LockInterruptiblyTest.lambda$main$1(LockInterruptiblyTest.java:43)
// at java.lang.Thread.run(Thread.java:748)
// 线程【thread1】:好累啊,干了10秒钟的活
// 线程【thread1】:收工回家,我要释放锁了
使用 lock() 方法输出如下
// 主线程:我来启动线程【thread1】了
// 主线程:好累啊,先睡个3秒钟,再去启动线程【thread2】吧
// 线程【thread1】:我来获取锁了
// 线程【thread1】:我获取到锁了,准备干活了!
// 主线程:我来启动线程【thread2】了
// 线程【thread2】:诶呀,获取不到锁,先睡个3秒钟吧,然后再看看情况
// 线程【thread2】:我来获取锁了
// 线程【thread2】:都睡了3秒钟了,还获取不到锁,不干了,我要中断了!
// 线程【thread1】:好累啊,干了10秒钟的活
// 线程【thread1】:收工回家,我要释放锁了
// 线程【thread2】:我获取到锁了,准备干活了!
// 线程【thread2】:好累啊,干了10秒钟的活
// 线程【thread2】:收工回家,我要释放锁了
如上所示,当某个线程使用 lock() 方法来获取锁时,如果锁已被另外的线程获取,则该线程会处于等待状态,若此时调用该线程的 interrupt() 方法,则会将该线程的中断标识设为 true,该线程会继续执行,不受影响,何时中断取决于该线程本身
当某个线程使用 lockInterruptibly() 方法来获取锁时,如果锁已被另外的线程获取,则该线程会处于等待状态,若此时调用该线程的 interrupt() 方法,则会中断该线程的等待而直接返回,并抛出 InterruptedException 异常
公平锁
public class FairLock implements Runnable {
private static ReentrantLock fairLock = new ReentrantLock(true);
@Override
public void run() {
IntStream.range(0, 5).forEach(i -> {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + " get lock");
} catch (Exception e) {
e.printStackTrace();
} finally {
fairLock.unlock();
}
});
}
public static void main(String[] args) {
FairLock fairLock = new FairLock();
Thread thread1 = new Thread(fairLock);
Thread thread2 = new Thread(fairLock);
thread1.start();
thread2.start();
}
}
// Thread-1 get lock
// Thread-0 get lock
// Thread-1 get lock
// Thread-0 get lock
// Thread-1 get lock
// Thread-0 get lock
// Thread-1 get lock
// Thread-0 get lock
// Thread-1 get lock
// Thread-0 get lock
非公平锁
public class NonFairLock implements Runnable {
private static ReentrantLock nonFairLock = new ReentrantLock(false);
@Override
public void run() {
IntStream.range(0, 5).forEach(i -> {
try {
nonFairLock.lock();
System.out.println(Thread.currentThread().getName() + " get lock");
} catch (Exception e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
}
});
}
public static void main(String[] args) {
NonFairLock nonFairLock = new NonFairLock();
Thread thread1 = new Thread(nonFairLock);
Thread thread2 = new Thread(nonFairLock);
thread1.start();
thread2.start();
}
}
// Thread-1 get lock
// Thread-1 get lock
// Thread-1 get lock
// Thread-1 get lock
// Thread-1 get lock
// Thread-0 get lock
// Thread-0 get lock
// Thread-0 get lock
// Thread-0 get lock
// Thread-0 get lock
锁绑定多个条件 (选择性通知)
public class LockCondition {
private int count = 0;
private boolean isOk = false;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void product() {
try {
lock.lock();
while (true) {
if (isOk) {
producer.await();
}
count++;
isOk = true;
System.out.println("producer【" + count + "】生产数据");
TimeUnit.SECONDS.sleep(1);
consumer.signal();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
try {
lock.lock();
while (true) {
if (!isOk) {
consumer.await();
}
isOk = false;
System.out.println("consumer【" + count + "】消费数据");
TimeUnit.SECONDS.sleep(1);
producer.signal();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
LockCondition lockCondition = new LockCondition();
Thread producer1 = new Thread(new ProducerThread(lockCondition));
Thread consumer1 = new Thread(new ConsumerThread(lockCondition));
Thread producer2 = new Thread(new ProducerThread(lockCondition));
Thread consumer2 = new Thread(new ConsumerThread(lockCondition));
producer1.start();
consumer1.start();
producer2.start();
consumer2.start();
}
}
class ProducerThread implements Runnable {
private LockCondition producer;
public ProducerThread(LockCondition producer) {
this.producer = producer;
}
@Override
public void run() {
producer.product();
}
}
class ConsumerThread implements Runnable {
private LockCondition consumer;
public ConsumerThread(LockCondition consumer) {
this.consumer = consumer;
}
@Override
public void run() {
consumer.consume();
}
}
如上所示,我们通过两个 Condition 对象来分别控制生产线程和消费线程,当生产线程生产数据后,消费线程才能消费数据,且不能重复消费
在 main 方法中,我们创建了两个生产线程和两个消费线程进行数据模拟,由结果可知,符合预期,避免了消费线程在唤醒线程时还是唤醒消费线程的情况 (此处调用 producer.signal()
,只会唤醒两个生产线程中的其中一个)
因此通过 ReentrantLock + Condition 的组合,我们可以实现线程的选择性通知
AQS的节点(Node)实现原理
Java并发之ReentrantLock详解
一步步透彻理解Lock的Acquire和Release原理源码