上一次咱们已经讲了AQS
,若是对其不熟悉的话建议先去看看其实现原理,看完再来看ReentrantLock
就很简单了。html
啃碎JDK源码(一):String
啃碎JDK源码(二):Integer
啃碎JDK源码(三):ArrayList
啃碎JDK源码(四):HashMap
啃碎JDK源码(五):ConcurrentHashMap
啃碎JDK源码(六):LinkedList
啃碎JDK源码(七):AbstractQueuedSynchronizer(AQS)java
像 ReentrantLock
和 Synchornized
在面试中常常被用来比较,若是想了解Synchronized
的话能够看我另一篇文章:死磕Synchronized面试
先来了解一下一些核心属性:segmentfault
public class ReentrantLock implements Lock, java.io.Serializable { // 实现AQS的内部类 private final Sync sync; ...... }
没错,ReentrantLock
没有什么值得注意的属性,由于已经在AQS
中定义好了,咱们只须要继承它而后进行简单的实现便可。性能
先看下 ReentrantLock
的用法:ui
public static void main(String[] args) { Lock lock = new ReentrantLock(); lock.lock(); try { // 执行业务 Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }
只要调用 lock
方法就能够进行加锁操做,表示接下来的这段代码已经被当前线程锁住,其余线程须要执行时须要拿到这个锁才能执行,而当前线程在执行完以后要显式的调用 unlock
释放锁。spa
注意:看源码以前你必需要对AQS
比较熟悉才行,能够参考我上一篇博客:
啃碎JDK源码(七):AbstractQueuedSynchronizer(AQS)操作系统
咱们来跟进源码看一下,先来看咱们的加锁lock
方法:线程
public void lock() { sync.lock(); } // Sync继承了AQS abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); ...... }
能够看到是调用内部类的lock
方法,而它是一个抽象方法,咱们看下谁继承了这个抽象接口:code
FairSync
和 NonfairSync
是 ReentrantLock
的另外两个内部类。顾名思义一个是公平锁,一个是非公平锁。(公平锁就是永远都是队列的第一位才能获得锁)
在AQS
有一个同步队列(CLH
),是一种先进先出队列。公平锁的意思就是严格按照这个队列的顺序来获取锁,非公平锁的意思就是不必定按照这个队列的顺序来。
在new对象的时候便会对sync初始化,以下:
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
能够看出默认是非公平锁,若是传true则初始化为公平锁。
那咱们首先来看看非公平锁:
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { // CAS修改状态 if (compareAndSetState(0, 1)) // 设置独占线程 setExclusiveOwnerThread(Thread.currentThread()); else // 进入队列等待 acquire(1); } // tryAcquire是AQS的抽象方法,咱们这里对其实现 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
首先用 compareAndSetState
方法使用CAS修改state状态变量的值,若是修改为功的话使用 setExclusiveOwnerThread(Thread.currentThread())
方法将当前线程设置为独占锁的持有线程,不然调用AQS的 acquire
方法进去队列等待处理。
接下来看一下acquire
方法:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
该方法是AQS里的方法,咱们上次已经介绍过了,这里直接截过来看下:
此次咱们主要关注由子类ReentrantLock
实现的tryAcquire
方法:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(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"); // 直接修改state遍历,由于已经持有锁,不须要用CAS去修改 setState(nextc); return true; } return false; }
上面代码和咱们在上次手动实现一个可重入锁的代码差很少,这里就再也不展开。
那接下来看一下 unlock
方法:
public void unlock() { sync.release(1); } public final boolean release(int arg) { // 尝试释放锁 if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
release
方法在AQS类中定义好了,咱们子类主要实现 tryRelease
方法:
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; }
这段代码上篇文章咱们也已经讲过了,若是忘记的同窗能够回头看看。
看完非公平锁的最后来看看公平锁的加锁方法:
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; } }
其实代码基本和前面同样,只是多了hasQueuedPredecessors
方法用来判断是否存在比等待更久的线程,由于要按照等待时间顺序获取资源,其它的这里就再也不细说了。
如下问题来自 从源码角度理解ReentrantLock
由FIFO队列的特性知,先加入同步队列等待的线程会比后加入的线程更靠近队列的头部,那么它将比后者更早的被唤醒,它也就能更早的获得锁。从这个意义上,对于在同步队列中等待的线程而言,它们得到锁的顺序和加入同步队列的顺序一致,这显然是一种公平模式。然而,线程并不是只有在加入队列后才有机会得到锁,哪怕同步队列中已有线程在等待,非公平锁的不公平之处就在于此。回看下非公平锁的加锁流程,线程在进入同步队列等待以前有两次抢占锁的机会:
compareAndSetState
方法尝试修改state变量,只有在当前锁未被任何线程占有(包括自身)时才能成功。tryAcquire(arg)
尝试获取锁。只有这两次获取锁都失败后,线程才会构造结点并加入同步队列等待。而线程释放锁时是先释放锁(修改state值),而后才唤醒后继结点的线程的。试想下这种状况,线程A已经释放锁,但还没来得及唤醒后继线程C,而这时另外一个线程B恰好尝试获取锁,此时锁刚好不被任何线程持有,它将成功获取锁而不用加入队列等待。线程C被唤醒尝试获取锁,而此时锁已经被线程B抢占,故而其获取失败并继续在队列中等待。
那咱们在开发中为何大多使用非公平锁?很简单,由于它性能好啊。
有关 ReentrantLock
的知识就介绍到这里了,有什么不对的地方请多多指教。