[关闭]
@kiraSally 2018-03-12T19:05:01.000000Z 字数 9239 阅读 2815

并发番@ReentrantLock一文通

JAVA 并发 1.8版


1.ReentrantLock定义

1.1 ReentrantLock综述

ReentrantLock是并发包中提供的独占互斥可重入锁,与Synchronized的对比就可发现其的拓展性之强:

锁实现机制不同: ReentrantLock 底层实现依赖于AQS(CAS+CLH),这与Syn的监视器模式截然不同

锁获取更加灵活: ReentrantLock 支持响应中断、超时、尝试获取锁,比Syn要灵活的多

锁释放形式不同: ReentrantLock 必须显示调用unlock()释放锁,而Syn则会自动释放监视器

公平策略支持: ReentrantLock 同时提供公平和非公平策略,以用于同时支持有序或吞吐量更大的执行方式,而Syn本身即非公平锁

条件队列支持: ReentrantLock 是基于AQS的独占模式实现,因此还提供对管程形式的条件队列的支持,而Syn则不支持条件队列

可重入支持: ReentrantLock 的可重入效果与Syn是一致的,区别是后者会自动释放锁


小问:神马是可重入?
友情小提示:之前有小伙伴曾问过笔者什么是可重入,笔者觉得还是有必要再告知一下读者

小答:所谓可重入指的是一个线程获取独占锁之后,可以再次多次获取并且多次释放;对于Synchronized来说可重入类似于"包装时在小盒子外面再包个大盒子,打开时也是从把大盒子打开再打开小盒子",即按加锁顺序依次解锁


1.2 最佳实践

  1. class X {
  2. //1.实例化一个ReentrantLock对象
  3. private final ReentrantLock lock = new ReentrantLock();
  4. public void m() {
  5. //2.获取锁 - 阻塞直到获取锁
  6. lock.lock();
  7. try {
  8. //3.doSomething...
  9. } finally {
  10. //4.释放锁 - 必须每次在finally中执行解锁操作!
  11. lock.unlock()
  12. }
  13. }
  14. }
  15. }

2.ReentrantLock组成

2.1 类定义

  1. public class ReentrantLock implements Lock, java.io.Serializable

2.2 构造器

  1. /**
  2. * 默认构造器 - 默认使用非公平策略
  3. * 该构造等价于ReentrantLock(false)
  4. */
  5. public ReentrantLock() {
  6. sync = new NonfairSync();
  7. }
  8. /**
  9. * 可选公平策略构造器
  10. * @param fair true:公平策略 false:非公平策略
  11. */
  12. public ReentrantLock(boolean fair) {
  13. sync = fair ? new FairSync() : new NonfairSync();
  14. }

2.3 重要变量

  1. /** 同步器提供所有的实现方法,注意sync实例一旦生成就不可变 - 默认非公平 */
  2. private final Sync sync;

2.4 锁方法

  1. /**
  2. * 不响应中断获取锁
  3. */
  4. public void lock() {
  5. sync.lock();
  6. }
  7. /**
  8. * 响应中断获取锁
  9. */
  10. public void lockInterruptibly() throws InterruptedException {
  11. sync.acquireInterruptibly(1);
  12. }
  13. /**
  14. * 尝试获取锁
  15. */
  16. public boolean tryLock() {
  17. return sync.nonfairTryAcquire(1);
  18. }
  19. /**
  20. * 响应超时中断的尝试获取锁
  21. */
  22. public boolean tryLock(long timeout, TimeUnit unit)
  23. throws InterruptedException {
  24. return sync.tryAcquireNanos(1, unit.toNanos(timeout));
  25. }
  26. /**
  27. * 释放锁
  28. */
  29. public void unlock() {
  30. sync.release(1);
  31. }

2.5 其他方法

  1. /**
  2. * 获取当前线程持有锁的次数
  3. */
  4. public int getHoldCount() {
  5. return sync.getHoldCount();
  6. }
  7. /**
  8. * 判断持有锁的线程是否为当前线程
  9. * 注意虽然只是名字区别,但这侧面告诉我们可重入锁是独占模式
  10. */
  11. public boolean isHeldByCurrentThread() {
  12. return sync.isHeldExclusively();
  13. }
  14. /**
  15. * 判断锁是否已被持有
  16. * 该方法只是个监控方法
  17. */
  18. public boolean isLocked() {
  19. return sync.isLocked();
  20. }
  21. /**
  22. * 是否是公平模式 - 默认非公平
  23. */
  24. public final boolean isFair() {
  25. return sync instanceof FairSync;
  26. }
  27. /**
  28. * 判断同步队列是否非空
  29. * 注意:即使返回true,也不能认为仍有线程想要获取锁
  30. * 原因在于队列可能存在被取消的节点
  31. * 该方法只是个监控方法
  32. */
  33. public final boolean hasQueuedThreads() {
  34. return sync.hasQueuedThreads();
  35. }
  36. /**
  37. * 判断指定线程是否在同步队列中
  38. * 注意:即使返回true,也不能认为该线程想要获取锁
  39. * 原因在于该线程可能被取消但仍在队列中
  40. * 该方法只是个监控方法
  41. */
  42. public final boolean hasQueuedThread(Thread thread) {
  43. return sync.isQueued(thread);
  44. }
  45. /**
  46. * 获取同步队列中节点数量
  47. * 注意:该值只是个估计值,因为队列随时会动态变化
  48. * 该方法只是个监控方法
  49. */
  50. public final int getQueueLength() {
  51. return sync.getQueueLength();
  52. }

2.6 条件队列

  1. /**
  2. * 支持管程形式的条件队列
  3. */
  4. public Condition newCondition() {
  5. return sync.newCondition();
  6. }
  7. /**
  8. * 判断指定条件队列中是否非空
  9. * 注意:即使返回true,也不能认为之后的signal操作一定能唤醒节点
  10. * 原因在于队列中随时可能发生中断或超时事件
  11. * 该方法只是个监控方法
  12. */
  13. public boolean hasWaiters(Condition condition) {
  14. if (condition == null)
  15. throw new NullPointerException();
  16. if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
  17. throw new IllegalArgumentException("not owner");
  18. return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
  19. }
  20. /**
  21. * 获取指定条件队列中节点数量
  22. * 注意:该值只是个估计值,因为队列随时会动态变化
  23. * 该方法只是个监控方法
  24. */
  25. public int getWaitQueueLength(Condition condition) {
  26. if (condition == null)
  27. throw new NullPointerException();
  28. if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
  29. throw new IllegalArgumentException("not owner");
  30. return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
  31. }

3.Sync - 同步器

3.1 类定义

  1. /**
  2. * Base of synchronization control for this lock. Subclassed
  3. * into fair and nonfair versions below. Uses AQS state to
  4. * represent the number of holds on the lock.
  5. *
  6. * 1.可重入锁的同步控制实现基础
  7. * 2.其对公平和非公平版本提供基础支持
  8. * 3.其使用AQS的state字段描述线程持有锁的次数,因此其继承了AQS
  9. * 注意:锁机制的实现都是基于AQS,因此读者务必先理解一下AQS
  10. */
  11. abstract static class Sync extends AbstractQueuedSynchronizer

小问:为神马Sync是静态抽象内部类?
友情小提示:虽然这题比较简单,但仔细想想这思路设计还是有助于我们优化代码的

小答:让我们想想静态内部抽象,同时结合AQS的模板模式思考一下:
静态内部:静态内部类的作用是告诉读者其内部类不需要依赖外部类的实例,即相当于一个静态变量成员
抽象:这里的抽象主要是为了与Lock接口的lock()方法保持一致,拓展自定义实现的灵活性
模板模式:AQS类本身就是个抽象类,其定义了很多方法供子类实现,而Sync即是其子类;同时有心的读者会发现在其他并发类也有同名Sync类实现,如CountDownLatch等,因此静态内部还有个命名空间的作用,也算是模板模式的一种变相实现

3.2 核心方法

3.2.1 lock

  1. /**
  2. * Performs {@link Lock#lock}. The main reason for subclassing
  3. * is to allow fast path for nonfair version.
  4. *
  5. * 该抽象方法是为了个Lock接口的lock()方法保持一致
  6. * 之所以提供给子类实现是为了提供非公平版本快速通过的方式
  7. */
  8. abstract void lock();

3.2.2 nonfairTryAcquire

加锁主要做了两件事情:
1.CAS更新状态(状态值会减增加)
2.设置锁关联线程
  1. /**
  2. * Performs non-fair tryLock. tryAcquire is implemented in
  3. * subclasses, but both need nonfair try for trylock method.
  4. * 尝试获取锁 - 非公平版本
  5. * @param acquires 想要获取锁的数量
  6. */
  7. final boolean nonfairTryAcquire(int acquires) {
  8. //1.获取当前线程 - 值得注意的是使用final以确保引用不变
  9. final Thread current = Thread.currentThread();
  10. //2.获取当前线程状态
  11. int c = getState();
  12. //3.当State=0时意味着锁可能还未被占用
  13. if (c == 0) {
  14. /**
  15. * 4.CAS更新锁状态
  16. * - 由于getState和更新操作之间可能存在并发,因此必须使用CAS
  17. * - 一旦CAS失败,说明在getState到CAS这段时间内其他锁已经成功获取锁了
  18. */
  19. if (compareAndSetState(0, acquires)) {
  20. //5.除了更新CAS状态,确保线程持有锁的另一个关键步骤就是设置锁关联线程
  21. setExclusiveOwnerThread(current);
  22. //6.当成功持有锁后返回true
  23. return true;
  24. }
  25. }
  26. //7.若当前线程就是持有锁的线程,此时即为可重入情况
  27. else if (current == getExclusiveOwnerThread()) {
  28. //8.可重入次数累加
  29. int nextc = c + acquires;
  30. if (nextc < 0) // overflow
  31. throw new Error("Maximum lock count exceeded");
  32. /**
  33. * 9.设置锁状态
  34. * - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销
  35. */
  36. setState(nextc);
  37. //10.可重入锁立即返回true
  38. return true;
  39. }
  40. //11.获取锁失败返回false
  41. return false;
  42. }

3.2.3 tryRelease

解锁主要做了两件事情:
1.CAS更新状态(状态值会减少)
2.解除锁与当前持有线程的关联

注意:在可重入情况下,在锁还没释放完毕时tryRelease可能返回false

  1. /**
  2. * 释放锁 - 注意对可重入的支持
  3. * @param acquires 想要释放锁的数量
  4. */
  5. protected final boolean tryRelease(int releases) {
  6. /**
  7. * 1.减少可重入次数
  8. * - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销
  9. */
  10. int c = getState() - releases;
  11. //2.注意:只有持有锁的线程才能释放锁!!否则直接抛出异常
  12. if (Thread.currentThread() != getExclusiveOwnerThread())
  13. throw new IllegalMonitorStateException();
  14. /**
  15. * 3.清空锁关联线程 - 即解除锁与当前线程的关联
  16. * - 值得注意的是只有可重入锁完全释放完毕才会返回true
  17. */
  18. boolean free = false;
  19. if (c == 0) {
  20. free = true;
  21. setExclusiveOwnerThread(null);
  22. }
  23. /**
  24. * 4.设置新的锁状态
  25. * - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销
  26. */
  27. setState(c);
  28. //5.只有可重入锁完全释放完毕才会返回true
  29. return free;
  30. }

3.3 重要方法

  1. /**
  2. * 判断锁是否已被持有
  3. * 当state=0时说明锁尚未被任何线程锁持有
  4. */
  5. final boolean isLocked() {
  6. return getState() != 0;
  7. }
  8. /**
  9. * 获取持有锁的线程
  10. * 当state=0时说明锁尚未被任何线程锁持有
  11. */
  12. final Thread getOwner() {
  13. return getState() == 0 ? null : getExclusiveOwnerThread();
  14. }
  15. /**
  16. * 判断持有锁的线程是否为当前线程
  17. */
  18. protected final boolean isHeldExclusively() {
  19. return getExclusiveOwnerThread() == Thread.currentThread();
  20. }
  21. /**
  22. * 获取当前线程持有锁的次数
  23. * 若当前线程未持有锁,直接返回0
  24. */
  25. final int getHoldCount() {
  26. return isHeldExclusively() ? getState() : 0;
  27. }
  28. /**
  29. * 支持管程形式的条件队列
  30. * 这也间接说明可重入锁是独占模式锁
  31. */
  32. final ConditionObject newCondition() {
  33. return new ConditionObject();
  34. }

4.NonfairSync - 非公平锁

非公平锁:加锁时无需考虑之前是否有线程等待,直接尝试获取锁,获取失败会自动追加到同步队列队尾

4.1 类定义

  1. static final class NonfairSync extends Sync

4.2 核心方法

4.2.1 lock

  1. /**
  2. * Performs lock. Try immediate barge, backing up to normal
  3. * acquire on failure.
  4. */
  5. final void lock() {
  6. /**
  7. * 1.CAS更新锁状态 0->1
  8. * 这里算是一种优化:
  9. * 若锁尚未被任何线程持有时,可以通过CAS快速更新锁状态
  10. * 一旦成功随后设置锁关联线程即可真正获取到锁
  11. * 注意:非公平下直接竞争
  12. */
  13. if (compareAndSetState(0, 1))
  14. //2.设置锁关联线程
  15. setExclusiveOwnerThread(Thread.currentThread());
  16. else
  17. //3.调用AQS的aquire方法:处理可重入和锁可能已被占据的情况
  18. acquire(1);
  19. }

4.2.2 tryAcquire

  1. /**
  2. * 尝试获取锁 - 非公平版本
  3. * - 该方法是AQS类的抽象方法tryAcquire()方法的子类实现,主要供AQS使用
  4. * - 非公平策略会直接调用Sync的nonfairTryAcquire()方法
  5. * @param acquires 想要获取锁的数量
  6. */
  7. protected final boolean tryAcquire(int acquires) {
  8. return nonfairTryAcquire(acquires);
  9. }

5.FairSync - 公平锁

公平锁:加锁钱需要检查是否还有在排队(等待)的线程,优先排队的

5.1 类定义

  1. static final class FairSync extends Sync

5.2 核心方法

5.2.1 lock

  1. /**
  2. * 获取锁 - 公平版本
  3. * - 该方法是Sync类的抽象方法lock()方法的子类实现
  4. * - 该方法直接调用AQS提供的aquire()方法
  5. */
  6. final void lock() {
  7. //每次只获取一个资源
  8. acquire(1);
  9. }

5.2.2 tryAcquire

  1. /**
  2. * 尝试获取锁 - 公平版本
  3. * - 该方法是AQS类的抽象方法tryAcquire()方法的子类实现,主要供AQS使用
  4. * - 只有当递归调用(结束)、没有等待者或是第一个等待者时才可获得锁,
  5. * 简单来说就是前面没人了,该轮到他了
  6. * @param acquires 想要获取锁的数量
  7. */
  8. protected final boolean tryAcquire(int acquires) {
  9. //1.获取当前线程 - 值得注意的是使用final以确保引用不变
  10. final Thread current = Thread.currentThread();
  11. //2.获取当前线程状态
  12. int c = getState();
  13. //3.当State=0时意味着锁可能还未被占用
  14. if (c == 0) {
  15. /**
  16. * 4.同nonfairTryAcquire的重要区别!
  17. * 同时也是公平策略的实现关键:
  18. * - 只有当该线程前面已经没有任何等待者时,当前线程才有资格去获取锁
  19. * - 公平下并不是直接竞争,而是先检查一下在同步队列中它之前还有木有在排队等待的节点
  20. */
  21. if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
  22. setExclusiveOwnerThread(current);
  23. return true;
  24. }
  25. }
  26. //5.若当前线程就是持有锁的线程,此时即为可重入情况
  27. else if (current == getExclusiveOwnerThread()) {
  28. //6.可重入次数累加
  29. int nextc = c + acquires;
  30. if (nextc < 0)
  31. throw new Error("Maximum lock count exceeded");
  32. /**
  33. * 7.设置锁状态
  34. * - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销
  35. */
  36. setState(nextc);
  37. //8.可重入锁立即返回true
  38. return true;
  39. }
  40. //9.获取锁失败返回false
  41. return false;
  42. }
  43. /**
  44. * 判断同步队列中是否有比当前线程等待时间更长的线程
  45. * - 该方法等价于 getFirstQueuedThread() != Thread.currentThread() && hasQueuedThreads()
  46. * - 该方法专门被设计给公平策略,目的是阻止节点转移
  47. */
  48. public final boolean hasQueuedPredecessors() {
  49. Node t = tail;
  50. Node h = head;
  51. Node s;
  52. //这里也可以体现CLH锁变种的优越性,只要简单的比较head就可知前面是不是还有线程在等待
  53. return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
  54. }

小问:ReentrantLock是如何实现公平的?
友情小提示:关键在于hasQueuedPredecessors()的使用上面

小答: 有心的读者可能已经发现了,在步骤4时使用了&&操作,即前者必须满足条件后才执行CAS;同时结合tryAquire()在AQS中使用的方式,就很容易知道公平的实现,但为了方便起见,笔者还是用例子简单说一下AQS的实现过程:

基本要素:
有一个变量state=0;假设当前线程为A,每当A获取一次锁,state+1;A释放一次锁,state-1;同时锁会设置/清空关联线程;
基本流程:
假设当前线程A持有锁,此时state增加并>0;此时线程B尝试获取锁,若(1|N)次执行CAS(0,1)失败,线程会加入同步队列的队尾并被挂起等待;当A完全释放锁时,state减少并=0,同时唤醒同步队列的头节点(假设此时是B),B被唤醒后会去尝试CAS(0,1);刚好线程C也尝试去竞争这个锁,下面就有两种实现方式:
非公平锁实现:
线程C直接尝试CAS(0,1),若成功更新成功,则B就获取失败,那么要再次挂起 - 明明是B在C之前就尝试获取锁了,但反而是C先抢到了锁;
公平锁实现:
线程C会先检查同步队列中是否有比当前线程等待时间更长的线程,有的话就不执行CAS了,直接进入等待队列并挂起等待,这样B就能获取到锁了.

结论:因此公平和非公平的区别根本在于锁被释放时(state=0时),还未入队的线程与已入队且刚被唤醒的等待线程(头节点)之间谁先执行CAS,先执行的先成功获取锁嘛,再补充一点,同步队列本身就是FIFO,因此不要弄错谁才是需要比较公平的对象...


并发番@ReentrantLock一文通黄志鹏kira 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议 进行许可。

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注