概述 ReentrantLock是一个可重入的互斥锁,也被称为独占锁。它支持公平锁和非公平锁两种模式。
ReentrantLock的使用方法 下面看一个最初级的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Test { ReentrantLock lock = new ReentrantLock (); public void myMethor () { lock.lock(); lock.unlock(); } }
在进入方法后,在需要加锁的一些操作执行之前需要调用lock方法,在jdk文档中对lock方法详细解释如下:
获得锁。 如果锁没有被另一个线程占用并且立即返回,则将锁定计数设置为1。 如果当前线程已经保持锁定,则保持计数增加1,该方法立即返回。 如果锁被另一个线程保持,则当前线程将被禁用以进行线程调度,并且在锁定已被获取之前处于休眠状态,此时锁定保持计数被设置为1。
这里也很好的解释了什么是可重入锁,如果一个线程已经持有了锁,它再次请求获取自己已经拿到的锁,是能够获取成功的,这就是可重入锁。
在需要加锁的代码执行完毕之后,就会调用unlock释放掉锁。在jdk文档之中对,unlock的解释如下:
尝试释放此锁。 如果当前线程是该锁的持有者,则保持计数递减。 如果保持计数现在为零,则锁定被释放。 如果当前线程不是该锁的持有者,则抛出IllegalMonitorStateException 。
在这里有一个需要注意的地点,lock和unlock都反复提到了一个计数,这主要是因为ReentrantLock是可重入的。每次获取锁(重入)就将计数器加一,每次释放的时候的计数器减一,直到计数器为0,就将锁释放掉了。
以上就是最基础,最简单的使用方法。其余的一些方法,都是一些拓展的功能,查看jdk文档即可知道如何使用。
源码分析 继承体系 可以看出ReentrantLock继承自AQS并实现了Lock接口。它内部有公平锁和非公平锁两种实现,这两种实现都是继承自Sync。根据ReentrantLock决定到底采用公平锁还是非公平锁实现。
1 2 3 public ReentrantLock (boolean fair) { sync = fair ? new FairSync () : new NonfairSync (); }
核心方法源码分析 Lock方法
首先调用具体的Lock实现.sync可能是非公平锁实现也可能是公平锁实现,这取决于你new对象时的参数。
1 2 3 public void lock () { sync.lock(); }
我们以非公平锁实现来看下面的下面的代码。
非公平锁的lock方法的具体实现如下
1 2 3 4 5 6 final void lock () { if (compareAndSetState(0 , 1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1 ); }
首先进来就是一个判断,其中判断的条件就是compareAndSetState(0, 1).毫无疑问这是一个CAS。它的意思时如果当前的state的值的为0就将1与其交换(可以理解为将1赋值给0)并返回true。其实在这一步如果state的值修改成功了,那么锁就获取成功了。setExclusiveOwnerThread(Thread.currentThread())这行代码就是将当前线程设置为该排他锁的拥有者。
如果CAS失败了,那么就调用acquire(1);
如果初次获得锁失败就调用qcquire(1) 这个方法的具体实现如下;
1 2 3 4 5 public final void acquire (int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
这个方法进来首先第一步就是调用tryAcquire(arg). 那么该方法是干什么的呢? 非公平锁实际是调用了这个实现:
1 2 3 protected final boolean tryAcquire (int acquires) { return nonfairTryAcquire(acquires); }
它具体的实现是在nonfairTryAcquire(acquires)中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; }
第二次尝试获取锁失败后,就进行下一步操作 我们再会过头看void acquire(int arg)首先尝试获取锁,获取成功就直接返回了,获取失败就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)进行排队。 这一行代码可以分为两部分看,一部分是addWaiter(Node.EXCLUSIVE)一部分是acquireQueued.我们先看addWaiter(Node.EXCLUSIVE)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private Node addWaiter (Node mode) { Node node = new Node (Thread.currentThread(), mode); Node pred = tail; if (pred != null ) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
初始化对立对入队的具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Node enq (final Node node) { for (;;) { Node t = tail; if (t == null ) { if (compareAndSetHead(new Node ())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
这里稍微补充一下这个AQS中的这个等待队列。
节点也创建了,等待队列也入了 现在该看boolean acquireQueued(final Node node, int arg)方法了。 这个方法的具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 final boolean acquireQueued (final Node node, int arg) { boolean failed = true ; try { boolean interrupted = false ; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null ; failed = false ; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true ; } } finally { if (failed) cancelAcquire(node); } }
unlock方法
和加锁类似,调用具体的实现
1 2 3 public void unlock () { sync.release(1 ); }
具体的release实现
1 2 3 4 5 6 7 8 9 10 public final boolean release (int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0 ) unparkSuccessor(h); return true ; } return false ; }
该方法首先就调用了tryRelease(arg)方法,这个方法就是实现释放资源的关键。释放的具体操作,也印证了在jdk文档之中的关于unlock和lock的说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
如果还有线程在等待锁资源,那么就可以唤醒它们了 回到boolean release(int arg)看
1 2 3 if (h != null && h.waitStatus != 0 ) unparkSuccessor(h);
ReentrantLock的高阶使用方法 我们使用synchronized的时候,可以通过wait和notify来让线程等待,和唤醒线程。在ReentrantLock中,我们也可以使用Condition中的await和signal来使线程等待和唤醒。 以下面这段代码来解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class Test { static ReentrantLock lock = new ReentrantLock (); static Condition condition = lock.newCondition(); public static class TaskA implements Runnable { @Override public void run () { lock.lock(); System.out.println(Thread.currentThread().getName() + "开始执行" ); try { System.out.println(Thread.currentThread().getName() + "准备释放掉锁并等待" ); condition.await(); System.out.println(Thread.currentThread().getName() + "重新拿到锁并执行" ); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } public static class TaskB implements Runnable { @Override public void run () { lock.lock(); System.out.println(Thread.currentThread().getName() + "开始执行" ); System.out.println(Thread.currentThread().getName() + "开始唤醒等待的线程" ); condition.signal(); try { Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName() + "任务执行完毕" ); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } } public static void main (String[] args) { Thread taskA = new Thread (new TaskA (),"taskA" ); Thread taskB = new Thread (new TaskB (),"taskB" ); taskA.start(); taskB.start(); } }
输出结果:
1 2 3 4 5 6 taskA开始执行 taskA准备释放掉锁并等待 taskB开始执行 taskB开始唤醒等待的线程 taskB任务执行完毕 taskA重新拿到锁并执行
现象解释: 首先taskA拿到锁,并执行,到condition.await();释放锁,并进入阻塞。taskB因此拿到刚才taskA释放掉的锁,taskB开始执行。taskB执行到condition.signal();唤醒了taskA,taskB继续执行,taskA因为拿不到锁,因此虽然已经被唤醒了,但是还是要等到taskB执行完毕,释放锁后,才有机会拿到锁,执行自己的代码。
那么这个过程,源码到底是如何实现的呢?
Condition源码分析 await()的源码分析 具体的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public final void await () throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException (); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0 ; while (!isOnSyncQueue(node)) { LockSupport.park(this ); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0 ) break ; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null ) unlinkCancelledWaiters(); if (interruptMode != 0 ) reportInterruptAfterWait(interruptMode); }
基本的步骤如下:
首先判断线程是否被中断,如果中断则抛出InterruptedException()异常
添加当前线程到条件队列中去,然后释放掉所有的资源
如果当前线程不在等待队列中,就直接park阻塞当前线程
signal()方法源码分析 具体的实现代码如下:
1 2 3 4 5 6 7 public final void signal () { if (!isHeldExclusively()) throw new IllegalMonitorStateException (); Node first = firstWaiter; if (first != null ) doSignal(first); }
这个方法中最重要的也就是doSignal(first). 它的实现如下:
1 2 3 4 5 6 7 8 private void doSignal (Node first) { do { if ( (firstWaiter = first.nextWaiter) == null ) lastWaiter = null ; first.nextWaiter = null ; } while (!transferForSignal(first) && (first = firstWaiter) != null ); }
该方法所做的事情就是从等待队列中移除指定节点,并将其加入等待队列中去。 转移节点的方法实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 final boolean transferForSignal (Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0 )) return false ; Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true ; }
至此ReentrantLock的源码分析就结束了!