相关文章:
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; }
- 由上可知,如果锁状态 (state) 为
0
时,会进行 CAS 操作来获取独占锁,若操作成功,则将当前线程设置为该锁的唯一拥有者并返回 true;如果持有锁的线程为当前拥有独占锁的线程,则通过累加状态标识nextc
来记录重入次数,并返回 true;若都不是,则返回 false
- 由上可知,如果锁状态 (state) 为
-
三、公平锁
-
FairSync 继承自 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;若都不是,则返回 falsepublic 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
- 如上所示,在使用了公平锁的情况下,线程 1 和线程 2 交替获取独占锁
-
非公平锁
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
- 如上所示,在使用了非公平锁的情况下,线程 1 和线程 2 竞争获取独占锁
-
锁绑定多个条件 (选择性通知)
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 的组合,我们可以实现线程的选择性通知
-