@kiraSally
2018-03-12T11:05:01.000000Z
字数 9239
阅读 3333
JAVA 并发 1.8版
- 备注:鉴于JDK文档的英文注释过多,因此笔者选择摘录部分笔者认为最重要的部分,其他内容会以中文注释展现
- 推荐:笔者建议读者结合 并发番@Synchronized一文通(1.8版) 理解
- 推荐:笔者建议读者结合 并发番@AbstractQueuedSynchronizer一文通 理解
- 感谢支持:笔者个人博客 kiraSally的掘金个人博客
锁实现机制不同: ReentrantLock 底层实现依赖于AQS(CAS+CLH),这与Syn的监视器模式截然不同
锁获取更加灵活: ReentrantLock 支持响应中断、超时、尝试获取锁,比Syn要灵活的多
锁释放形式不同: ReentrantLock 必须显示调用unlock()释放锁,而Syn则会自动释放监视器
公平策略支持: ReentrantLock 同时提供公平和非公平策略,以用于同时支持有序或吞吐量更大的执行方式,而Syn本身即非公平锁
条件队列支持: ReentrantLock 是基于AQS的独占模式实现,因此还提供对管程形式的条件队列的支持,而Syn则不支持条件队列
可重入支持: ReentrantLock 的可重入效果与Syn是一致的,区别是后者会自动释放锁
小问:神马是可重入?
友情小提示:之前有小伙伴曾问过笔者什么是可重入,笔者觉得还是有必要再告知一下读者
小答:所谓可重入指的是一个线程获取独占锁之后,可以再次多次获取并且多次释放;对于Synchronized来说可重入类似于"包装时在小盒子外面再包个大盒子,打开时也是从把大盒子打开再打开小盒子",即按加锁顺序依次解锁
class X {//1.实例化一个ReentrantLock对象private final ReentrantLock lock = new ReentrantLock();public void m() {//2.获取锁 - 阻塞直到获取锁lock.lock();try {//3.doSomething...} finally {//4.释放锁 - 必须每次在finally中执行解锁操作!lock.unlock()}}}}
public class ReentrantLock implements Lock, java.io.Serializable
/*** 默认构造器 - 默认使用非公平策略* 该构造等价于ReentrantLock(false)*/public ReentrantLock() {sync = new NonfairSync();}/*** 可选公平策略构造器* @param fair true:公平策略 false:非公平策略*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
/** 同步器提供所有的实现方法,注意sync实例一旦生成就不可变 - 默认非公平 */private final Sync sync;
/*** 不响应中断获取锁*/public void lock() {sync.lock();}/*** 响应中断获取锁*/public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}/*** 尝试获取锁*/public boolean tryLock() {return sync.nonfairTryAcquire(1);}/*** 响应超时中断的尝试获取锁*/public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));}/*** 释放锁*/public void unlock() {sync.release(1);}
/*** 获取当前线程持有锁的次数*/public int getHoldCount() {return sync.getHoldCount();}/*** 判断持有锁的线程是否为当前线程* 注意虽然只是名字区别,但这侧面告诉我们可重入锁是独占模式*/public boolean isHeldByCurrentThread() {return sync.isHeldExclusively();}/*** 判断锁是否已被持有* 该方法只是个监控方法*/public boolean isLocked() {return sync.isLocked();}/*** 是否是公平模式 - 默认非公平*/public final boolean isFair() {return sync instanceof FairSync;}/*** 判断同步队列是否非空* 注意:即使返回true,也不能认为仍有线程想要获取锁* 原因在于队列可能存在被取消的节点* 该方法只是个监控方法*/public final boolean hasQueuedThreads() {return sync.hasQueuedThreads();}/*** 判断指定线程是否在同步队列中* 注意:即使返回true,也不能认为该线程想要获取锁* 原因在于该线程可能被取消但仍在队列中* 该方法只是个监控方法*/public final boolean hasQueuedThread(Thread thread) {return sync.isQueued(thread);}/*** 获取同步队列中节点数量* 注意:该值只是个估计值,因为队列随时会动态变化* 该方法只是个监控方法*/public final int getQueueLength() {return sync.getQueueLength();}
/*** 支持管程形式的条件队列*/public Condition newCondition() {return sync.newCondition();}/*** 判断指定条件队列中是否非空* 注意:即使返回true,也不能认为之后的signal操作一定能唤醒节点* 原因在于队列中随时可能发生中断或超时事件* 该方法只是个监控方法*/public boolean hasWaiters(Condition condition) {if (condition == null)throw new NullPointerException();if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))throw new IllegalArgumentException("not owner");return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);}/*** 获取指定条件队列中节点数量* 注意:该值只是个估计值,因为队列随时会动态变化* 该方法只是个监控方法*/public int getWaitQueueLength(Condition condition) {if (condition == null)throw new NullPointerException();if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))throw new IllegalArgumentException("not owner");return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);}
/*** Base of synchronization control for this lock. Subclassed* into fair and nonfair versions below. Uses AQS state to* represent the number of holds on the lock.** 1.可重入锁的同步控制实现基础* 2.其对公平和非公平版本提供基础支持* 3.其使用AQS的state字段描述线程持有锁的次数,因此其继承了AQS* 注意:锁机制的实现都是基于AQS,因此读者务必先理解一下AQS*/abstract static class Sync extends AbstractQueuedSynchronizer
小问:为神马Sync是静态抽象内部类?
友情小提示:虽然这题比较简单,但仔细想想这思路设计还是有助于我们优化代码的
静态内部、抽象,同时结合AQS的模板模式思考一下:Lock接口的lock()方法保持一致,拓展自定义实现的灵活性Sync即是其子类;同时有心的读者会发现在其他并发类也有同名Sync类实现,如CountDownLatch等,因此静态内部还有个命名空间的作用,也算是模板模式的一种变相实现
/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.** 该抽象方法是为了个Lock接口的lock()方法保持一致* 之所以提供给子类实现是为了提供非公平版本快速通过的方式*/abstract void lock();
/*** Performs non-fair tryLock. tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.* 尝试获取锁 - 非公平版本* @param acquires 想要获取锁的数量*/final boolean nonfairTryAcquire(int acquires) {//1.获取当前线程 - 值得注意的是使用final以确保引用不变final Thread current = Thread.currentThread();//2.获取当前线程状态int c = getState();//3.当State=0时意味着锁可能还未被占用if (c == 0) {/*** 4.CAS更新锁状态* - 由于getState和更新操作之间可能存在并发,因此必须使用CAS* - 一旦CAS失败,说明在getState到CAS这段时间内其他锁已经成功获取锁了*/if (compareAndSetState(0, acquires)) {//5.除了更新CAS状态,确保线程持有锁的另一个关键步骤就是设置锁关联线程setExclusiveOwnerThread(current);//6.当成功持有锁后返回truereturn true;}}//7.若当前线程就是持有锁的线程,此时即为可重入情况else if (current == getExclusiveOwnerThread()) {//8.可重入次数累加int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");/*** 9.设置锁状态* - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销*/setState(nextc);//10.可重入锁立即返回truereturn true;}//11.获取锁失败返回falsereturn false;}
注意:在可重入情况下,在锁还没释放完毕时tryRelease可能返回false
/*** 释放锁 - 注意对可重入的支持* @param acquires 想要释放锁的数量*/protected final boolean tryRelease(int releases) {/*** 1.减少可重入次数* - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销*/int c = getState() - releases;//2.注意:只有持有锁的线程才能释放锁!!否则直接抛出异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();/*** 3.清空锁关联线程 - 即解除锁与当前线程的关联* - 值得注意的是只有可重入锁完全释放完毕才会返回true*/boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}/*** 4.设置新的锁状态* - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销*/setState(c);//5.只有可重入锁完全释放完毕才会返回truereturn free;}
/*** 判断锁是否已被持有* 当state=0时说明锁尚未被任何线程锁持有*/final boolean isLocked() {return getState() != 0;}/*** 获取持有锁的线程* 当state=0时说明锁尚未被任何线程锁持有*/final Thread getOwner() {return getState() == 0 ? null : getExclusiveOwnerThread();}/*** 判断持有锁的线程是否为当前线程*/protected final boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();}/*** 获取当前线程持有锁的次数* 若当前线程未持有锁,直接返回0*/final int getHoldCount() {return isHeldExclusively() ? getState() : 0;}/*** 支持管程形式的条件队列* 这也间接说明可重入锁是独占模式锁*/final ConditionObject newCondition() {return new ConditionObject();}
非公平锁:加锁时无需考虑之前是否有线程等待,直接尝试获取锁,获取失败会自动追加到同步队列队尾
static final class NonfairSync extends Sync
/*** Performs lock. Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {/*** 1.CAS更新锁状态 0->1* 这里算是一种优化:* 若锁尚未被任何线程持有时,可以通过CAS快速更新锁状态* 一旦成功随后设置锁关联线程即可真正获取到锁* 注意:非公平下直接竞争*/if (compareAndSetState(0, 1))//2.设置锁关联线程setExclusiveOwnerThread(Thread.currentThread());else//3.调用AQS的aquire方法:处理可重入和锁可能已被占据的情况acquire(1);}
/*** 尝试获取锁 - 非公平版本* - 该方法是AQS类的抽象方法tryAcquire()方法的子类实现,主要供AQS使用* - 非公平策略会直接调用Sync的nonfairTryAcquire()方法* @param acquires 想要获取锁的数量*/protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
公平锁:加锁钱需要检查是否还有在排队(等待)的线程,优先排队的
static final class FairSync extends Sync
/*** 获取锁 - 公平版本* - 该方法是Sync类的抽象方法lock()方法的子类实现* - 该方法直接调用AQS提供的aquire()方法*/final void lock() {//每次只获取一个资源acquire(1);}
/*** 尝试获取锁 - 公平版本* - 该方法是AQS类的抽象方法tryAcquire()方法的子类实现,主要供AQS使用* - 只有当递归调用(结束)、没有等待者或是第一个等待者时才可获得锁,* 简单来说就是前面没人了,该轮到他了* @param acquires 想要获取锁的数量*/protected final boolean tryAcquire(int acquires) {//1.获取当前线程 - 值得注意的是使用final以确保引用不变final Thread current = Thread.currentThread();//2.获取当前线程状态int c = getState();//3.当State=0时意味着锁可能还未被占用if (c == 0) {/*** 4.同nonfairTryAcquire的重要区别!* 同时也是公平策略的实现关键:* - 只有当该线程前面已经没有任何等待者时,当前线程才有资格去获取锁* - 公平下并不是直接竞争,而是先检查一下在同步队列中它之前还有木有在排队等待的节点*/if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//5.若当前线程就是持有锁的线程,此时即为可重入情况else if (current == getExclusiveOwnerThread()) {//6.可重入次数累加int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");/*** 7.设置锁状态* - 值得注意的是此时没有使用CAS,原因在于此时线程已经持有锁,无须用CAS增加不必要的开销*/setState(nextc);//8.可重入锁立即返回truereturn true;}//9.获取锁失败返回falsereturn false;}/*** 判断同步队列中是否有比当前线程等待时间更长的线程* - 该方法等价于 getFirstQueuedThread() != Thread.currentThread() && hasQueuedThreads()* - 该方法专门被设计给公平策略,目的是阻止节点转移*/public final boolean hasQueuedPredecessors() {Node t = tail;Node h = head;Node s;//这里也可以体现CLH锁变种的优越性,只要简单的比较head就可知前面是不是还有线程在等待return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());}
小问:ReentrantLock是如何实现公平的?
友情小提示:关键在于hasQueuedPredecessors()的使用上面
小答: 有心的读者可能已经发现了,在步骤4时使用了&&操作,即前者必须满足条件后才执行CAS;同时结合tryAquire()在AQS中使用的方式,就很容易知道公平的实现,但为了方便起见,笔者还是用例子简单说一下AQS的实现过程:
结论:因此公平和非公平的区别根本在于锁被释放时(state=0时),还未入队的线程与已入队且刚被唤醒的等待线程(头节点)之间谁先执行CAS,先执行的先成功获取锁嘛,再补充一点,同步队列本身就是FIFO,因此不要弄错谁才是需要比较公平的对象...
并发番@ReentrantLock一文通 由 黄志鹏kira 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议 进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。