一、AbstractQueuedSynchronizer简介
AQS(AbstractQueuedSynchronizer)是并发容器JUC(java.util.concurrent)下locks包内的一个抽象类,是一个同步器,是用来构建锁或者其余同步组件的根底框架,外部保护了一个成员变量state
示意同步状态,state=0
示意线程未获取到锁,state > 0
示意获取到锁,state > 1
示意重入锁的数量,被 volatile
润饰保障了可见性,通过CAS操作对其批改,内置保护了FIFO
队列实现对未获取到锁的线程进行排队工作。
二、AbstractQueuedSynchronizer源码解析
核心成员变量
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { /** * 期待获取锁队列的头节点, 只能通过setHead办法批改 * 如果head存在保障waitStatus状态不为CANCELLED */ private transient volatile Node head; /** * 期待获取锁队列的尾节点, 只能通过enq办法增加新的期待节点 */ private transient volatile Node tail; /** * 示意锁的状态 * state = 0 示意未锁定 * state > 0 示意已锁定 * state > 1 示意可重入锁, 获取锁的次数 * volatile润饰保障了可见性 */ private volatile int state;}
AbstractQueuedSynchronizer次要有三个核心成员变量state
、head
、tail
state
:示意锁的状态, 等于0示意未锁定,大于0示意已锁定,大于1示意可重入锁,重入锁的次数。被volatile
润饰保障了可见性。head
:期待队列的头节点,除了初始化只能通过setHead()
办法设置值,如果head
存着能保障waitStatus
状态不为CANELLED
。tail
:期待队列尾节点,只能通过equ
增加新的期待节点。
Node节点
AbstractQueuedSynchronizer
外部保护着FIFO
队列,也就是CLH
队列,这个队列的每一个元素都是一个Node
,所以咱们接下来要理解其余其内部类Node
,源码如下:
private static class Node { /** * 节点正在共享模式下期待的标记 */ static final Node SHARED = new Node(); /** * 节点正在独占模式下期待的标记 */ static final Node EXCLUSIVE = null; /** * waitStatus变量的可选值, 勾销状态, 被勾销的节点不参加锁竞争, 状态也不会被扭转 */ static final int CANCELLED = 1; /** * waitStatus变量的可选值, 下一节点处于期待状态, 如果以后节点开释锁或者被勾销, 会告诉下一节点去运行 */ static final int SIGNAL = -1; /** * waitStatus变量的可选值, 示意节点处于condition队列中, 正在期待被唤醒 */ static final int CONDITION = -2; /** * waitStatus变量的可选值, 下一次acquireShared应该无条件流传 */ static final int PROPAGATE = -3; /** * 节点的期待状态 */ volatile int waitStatus; /** * 上一节点 */ volatile Node prev; /** * 下一节点 */ volatile Node next; /** * 获取同步状态(锁)的线程 */ volatile Thread thread; /** * 下一个condition队列的期待节点 */ Node nextWaiter; /** * 是否是共享模式 */ final boolean isShared() { return nextWaiter == SHARED; } /** * 获取前一节点, 前一节点为null会抛异样 */ final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) { throw new NullPointerException(); } else { return p; } } /** * 无参构造方法用于初始化头部或者共享模式标记 */ Node () { } /** * 用于addWaiter办法, 设置下一个condition队列的期待节点 */ Node(Thread thread, Node mode) { this.nextWaiter = mode; this.thread = thread; } /** * 用于addConditionWaiter办法 */ Node (Thread thread, int waitStatus) { this.thread = thread; this.waitStatus = waitStatus; }}
外围办法
JUC外面的工具类根本都是根底AQS实现的,比ReentrantLock
、CountDownLatch
、CyclicBarrier
、Semaphore
等,有的只反对独占锁,如ReentrantLock#lock()
,有的反对共享锁,如Semaphore
,从前文的Node类的定义也能看到
/** * 节点正在共享模式下期待的标记 */static final Node SHARED = new Node();/** * 节点正在独占模式下期待的标记 */static final Node EXCLUSIVE = null;
AQS实现了两套加锁解锁的形式,那就是独占锁和共享锁。咱们就从AQS最罕用的类ReentrantLock
来学习AQS的外围办法。
三、ReentrantLock
简介
ReentrantLock
是根底AQS实现的一个可重入且独占式锁。内置了一个Sync
同步器类实现了AQS,且反对偏心锁和非偏心锁,其实现类别离是FairSync
和NonfairSync
。
ReentrantLock
所有操作都是通过外围外部类Sync
操作,由子类FairSync
和NonfairSync
实现。
private final Sync sync;
ReentrantLock加锁过程
lock
lock()
就是加锁,该办法定义如下:
public void lock() { sync.lock();}
FairSync
和NonfairSync
具体实现:
// FairSync实现final void lock() { acquire(1);}// NofairSync实现 setExclusiveOwnerThread是父类AQSfinal void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);}protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread;}
能够看到非偏心锁多了一个compareAndSetState()
操作,通过CAS尝试批改锁状态state
的值,如果批改胜利设置以后线程以独占的形式获取了锁,批改失败执行的逻辑和偏心锁一样。
偏心锁和非偏心锁获取独占锁的外围逻辑都是acquire()
办法,接下来就看看这个办法。
acquire
acquire
该办法是父类AbstractQueuedSynchronizer
定义的办法,源码如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}
该办法次要调用tryAcquire
办法尝试获取锁,胜利返回true示意获取到了锁,如果失败就将线程封装成节点插入队尾。
tryAcquire
tryAcquire
办法在类AbstractQueuedSynchronizer
没有间接实现,采纳模版办法的设计模式交给子类实现,先看偏心锁FairSync
的实现,源码如下:
protected final boolean tryAcquire(int acquires) { // 获取以后线程 final Thread current = Thread.currentThread(); // 获取以后锁状态 state=0示意未锁定, state>0示意已锁定 int c = getState(); if (c == 0) { // 线程没有获取到锁 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 没有比以后线程期待更久的线程了, 通过CAS的形式批改state // 如果胜利则设置以后线程获取独占式锁 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 获取独占锁的线程就是以后线程, 示意重入 // 重入锁的实现 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // 批改state记录获取锁的次数 setState(nextc); return true; } return false;}
从下面源码能够看出该办法就是独占的形式获取锁,获取胜利后返回true,重入锁的逻辑也是在这里实现,次要通过批改state的值来记录获取锁的次数。
非偏心锁的实现大同小异就是少了!hasQueuedPredecessors()
的判断,因为是非偏心锁嘛,所以不须要判断阻塞工夫了。
acquire()
办法除了调用tryAcquire()
办法外还调用了acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
,这里有两个办法,咱们先看addWaiter()
办法。
addWaiter
该办法相当于把以后线程封装成一个节点Node,并退出队列,这个办法咱们在下面有写过,源码如下:
/** * Creates and enqueues node for current thread and given mode. * 为以后线程和给定模式创立并设置尾节点 * Node.EXCLUSIVE: 独占模式 * Node.SHARED: 共享模式 */private Node addWaiter(Node mode) { // 为以后线程创立节点 Npde node = new Node(Thread.currentThread(), mode); // 获取尾节点 Node pred = tail; // Try the fast path of enq; backup to full enq on failure // 如果队列曾经创立, 尝试疾速增加尾结点 if (pred == null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 疾速增加失败, 则调用enq enq(node); retur node;}
enq
enq办法是将节点退出队列队尾,必要时要进行初始化,通过自旋+CAS的形式保障线程平安和插入胜利。源码如下:
/*** Inserts node into queue, initializing if necessary. See picture above* 将节点插入队列,必要时进行初始化*/private Node enq(final Node node) { // 自旋 for(;;) { // 获取尾节点 Node t = tail; // 尾节点为null示意队列没有初始化 if (t == null) { // 设置头节点 if (compareAndSetHead(new Node())) { tail = head; } } else { // 队列曾经初始化, 设置新增加节点的前一节点是队列的尾节点 node.prev = t; // 设置尾节点 if (compareAndSetTail(t, node)) { // 设置队列的尾节点的下一节点是新增加的节点, 新增加的节点就插入尾节点了 t.next = node; return t; } } }}
能够看出该办法就是往队列插入尾节点,通过自旋+CAS的形式,须要留神的是该办法返回的Node节点不是新插入的节点,而是新插入节点的前一节点。
enq()
办法中调用的compareAndSetHead()
、compareAndSetTail()
办法如下:
/** * 通过CAS设置head值, 只在enq办法调用 */private final boolean compareAndSetHead(Node update) { return unsafe.companreAndSwapObject(this, headOffset, null, update);}/** * 通过CAS函数设置tail值,仅仅在enq办法中调用 */private final boolean compareAndSetTail(Node expect, Node update) { return unsafe.compareAndSwapObject(this, tailOffset, expect, update);}
acquireQueued
acquireQueued()
办法作用就是获取锁,如果没有获取到锁就让以后线程阻塞期待,源码如下:
/** * 想要获取锁的acquire办法,都会通过这个办法获取锁 * 循环通过tryAcquire办法一直去获取锁,如果没有获取胜利 * 就有可能调用parkAndCheckInterrupt办法,让以后线程阻塞 * 后果返回true,示意在线程期待的过程中,线程被中断了 */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; // 不须要调用cancelAcquire办法 failed = false; return interrupted; } // 以后节点不是头节点或者没有获取到锁 // shouldParkAfterFailedAcquire办法用于判断以后线程是否须要被阻塞 // 当p节点的状态是Node.SIGNAL时就会调用parkAndCheckInterrupt办法阻塞线程 // parkAndCheckInterrupt办法用于阻塞线程并且检测线程是否被中断 // 被阻塞的线程有两种被唤醒的办法: // 1. 在unparkSuccessor(Node node)办法,会唤醒被阻塞的node线程,返回false // 2. 以后线程被调用了interrupt办法,线程被唤醒,返回true // 在这里只是简略地将interrupted = true,没有跳出for的死循环,持续尝试获取锁 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) { interrupted = true; } } } finally { // failed为true,示意产生异样非正常退出 if (failed) // 将以后节点状态设置成CANCELLED, 示意以后节点曾经被勾销, 不须要唤醒了 cancelAcquire(node); }}
acquireQueued
办法次要流程如下:
- 通过
for(;;)
死循环自旋,直到node(以后)节点获取到锁。 - 获取以后节点的前一个节点p。
- 如果节点p是头节点,而后调用
tryAcquire()
尝试获取锁,如果获取胜利就将node节点设置成头节点而后返回。 - 如果节点p不是投节点或者获取锁失败,调用
shouldParkAfterFaildAcquired()
办法来决定是否要阻塞以后线程。 - 如果要阻塞以后线程,调用
parkAndCheckInterrupt()
办法阻塞以后线程。 - 如果以后线程产生异样,非正常退出,调用
cancelAcquire()
办法将以后节点的状态设置成勾销。
shouldParkAfterFailedAcquire
shouldParkAfterFailedAcquire()
用于判断以后线程是否须要阻塞,源码如下:
/** * 杜绝前一个节点pred的状态来判断以后线程是否须要被阻塞 */private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前一节点的状态 int ws = pred.waitStatus; if (ws == Node.SINGAL) { // 如果前一节点pred的状态是Node.SINGAL, 阐明以后线程须要被阻塞 return true; } if (ws > 0) { // 如果前一节点状态是Node.CANCELLED(大于0就是CANCELLED) // 示意前一节点所在线程曾经被唤醒了, 要从队列中移除CANCELLED的节点 // 所以从pred节点始终向前查找直到找到不是CANCELLED状态的节点, 并把该节点赋值给node的prev // 示意node节点的前一节点曾经扭转 do { node.prev = pred = pred.prev; } while(pred.waitStatus > 0); pred.next = node; } else { // 此时前一节点pred的状态只能是0或者PROPAGATE, 不可能是CONDITION状态 // CONDITION(这个是非凡状态,只在condition列表中节点中存在,CLH队列中不存在这个状态的节点) // 将前一个节点pred的状态设置成Node.SIGNAL, 这样子下一次循环时,就是间接阻塞以后线程 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false;}
这个办法是杜绝前一个节点状态来判断以后线程是否须要被阻塞,前一节点的状态也是在这个办法中批改的,通过compareAndSetWaitStatus()
办法。
shouldParkAfterFailedAcquire()
办法次要流程如下:
- 如果前一节点状态是
Node.SIGNAL
,则间接返回true
以后线程进入阻塞状态。 - 如果前一节点状态是
Node.CANCELLED
(大于0就是CANCELLED),示意前一个节点曾经被唤醒了,要从队列中挪动CANCELLED状态的节点,所以送pred节点始终向前查问不是CANCELLED状态的节点,并将该节点赋值成以后节点的前一节点,示意以后节点的前一节点发生变化,在acquireQueued()
办法中进行下一次循环。 - 不是后面两种状态,只能是
Node.SIGNAL
状态,批改前一节点的状态为Node.SIGNAL
,下一次循环时阻塞以后线程。
parkAndCheckInterrupt
该办法用于阻塞以后线程并检测线程是否被中断,源码如下:
/** * 阻塞以后线程,线程被唤醒后返回以后线程中断状态 */private final boolean parkAndCheckInterrupt() { // 阻塞以后线程 LockSupport.park(this); // 检测以后线程是否被中断(该办法会革除中断标识位) return Thread.interrupted();}
cancelAcquire
cancelAcquire()
办法在acquireQueued()
办法异样的时候调用,用于将以后节点的状态设置成CANCELLED,源码如下:
// 将node节点的状态设置成CANCELLED,示意node节点所在线程已勾销,不须要唤醒了。private void cancelAcquire(Node node) { // 如果node为null,就间接返回 if (node == null) return; //将获取锁节点的线程置空 node.thread = null; // 跳过那些已勾销的节点,在队列中找到在node节点后面的第一次状态不是已勾销的节点 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 记录pred原来的下一个节点,用于CAS函数更新时应用 Node predNext = pred.next; // Can use unconditional write instead of CAS here. // After this atomic step, other Nodes can skip past us. // Before, we are free of interference from other threads. // 将node节点状态设置为已勾销Node.CANCELLED; node.waitStatus = Node.CANCELLED; // 如果node节点是队列尾节点,那么就将pred节点设置为新的队列尾节点 if (node == tail && compareAndSetTail(node, pred)) { // 并且设置pred节点的下一个节点next为null compareAndSetNext(pred, predNext, null); } else { // If successor needs signal, try to set pred's next-link // so it will get one. Otherwise wake it up to propagate. int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { unparkSuccessor(node); } node.next = node; // help GC }}
加锁过程总结
- 首先调用
lock()
办法,这个办法有两个子类FairSync
和NofairSync
实现,示意偏心锁和非偏心锁,两个类的不同就是NofairSync
会间接调用compareAndSetStaus()
办法批改加锁状态,如果胜利以后线程获取到锁。 - 而后调用父类
AbstractQueuedSynchronized
的acquire()
办法获取锁。 acquire()
办法调用tryAcquire()
办法尝试获取锁,tryAcquire()
由子类FairSync
和NofairSync
实现别离调用fairTryAcquire()
和nonfairTryAcquire()
办法尝试获取锁。这两个办法外面实现了重入锁的逻辑,如果以后锁状态是未获取到锁,则调用CAS设置锁状态,如果是获取到锁状态则会判断获取锁的线程是否是以后线程,如果是则是重入锁的逻辑记录以后线程获取锁的次数。- 如果
tryAcquire()
办法调用获取锁失败,则会调用acquireQueued()
办法再获取锁或者进入阻塞状态,acquireQueued()
办法首先调用了addWaiter()
办法用于将以后线程封装成一个节点退出队列队尾,而后再调用acquireQueued()
办法获取锁或者进入阻塞状态,acquireQueued()
办法会通过自旋的形式杜绝以后节点状态判断是否进入阻塞状态。当别的线程开释锁的时候,可能唤醒这个线程,再调用tryAcquire()
办法获取锁。 - 如果产生异样,将以后节点状态设置成CANCELLED。
ReentrantLock开释锁过程
unlock
调用unlock()
办法开释锁,而后调用release()
办法,源码如下:
public void unlock() { sync.release(1);}
release
release
是AbstactQueuedSynchronized
定义的办法用于开释锁,源码如下:
/** * 在独占锁模式下, 开释锁 */public final boolean release(int arg) { // 调用tryRelease办法尝试开释锁, 由子类实现 if (tryRelease(arg)) { //尝试开释锁胜利 获取头节点 Node h = head; // 如果头节点不为null且状态不为勾销状态, 调用unparkSuccessor唤醒被阻塞的线程 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false;}
release()
开释锁流程如下:
- 调用
tryRelease()
办法尝试开释锁,返回true示意开释锁胜利,返回false示意还持有锁资源。 - 如果开释锁胜利了,且头节点不为null,就要唤醒被阻塞的线程,调用
unparkSuccessor()
办法唤醒一个期待的线程。
tryRelease
tryRelease
尝试开释锁办法是有子类实现的,上面是ReentrantLock
中Sync
的tryRelease()
办法实现:
protected final boolean tryRelease(int releases) { // c示意新的锁状态 int c = getState() - releases; // 如果以后线程不是获取独占锁的线程抛错 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); // 是否能够开释锁 boolean free = false; // 如果新的锁状态=0示意能够开释锁 if (c == 0) { free = true; // 获取独占锁的线程置为null setExclusiveOwnerThread(null); } // 锁状态设置成未锁定 setState(c); return free;}
tryRelease()
尝试开释锁流程如下:
- 首先获取新的锁状态
- 判断以后线程是否是获取独占锁的线程,如果不是抛异样。
- 如果新的锁状态是未锁定状态,获取独占锁的线程置为null,新的锁状态置为未锁定。
unparkSuccessor
unparkSuccessor()
办法用于唤醒node节点下一节点非勾销状态的节点所在线程,源码如下:
/** * 唤醒node节点下一节点非勾销状态的节点所在线程 */private void unparkSuccessor(Node node) { // 获取node节点的状态 int ws = node.waitStatus; // 如果状态小于0, 就将状态置为0, 示意这个node节点曾经实现了 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取节点下一节点 Node s = node.next; // 如果下一节点为null, 或者下一节点状态为勾销状态, 就要寻找下一个非勾销状态的节点 if (s == null || s.waitStatus > 0) { // 先将s设置成null, s不是非勾销状态的节点 s = null; // for(Node t = tail; t != null && t!= node; t = t.prev) //因为是从后向前遍历,所以一直笼罩找到的值,这样能力失去node节点后下一个非勾销状态的节点 if (t.waitStatus <= 0) s = t; // 如果s不为null,示意存在非勾销状态的节点,那么调用LockSupport.unpark办法唤醒这个节点的线程 if (s != null) LockSupport.unpark(s.thread); }}
unparkSuccessor()
办法唤醒node节点的下一个非勾销状态的节点所在线程流程如下:
- 先将node节点的状态设置为0。
- 寻找下一个状态不为勾销的节点s。
- 如果节点s不为null,调用
LockSupport.unpark()
办法唤醒s所在线程。
开释锁过程总结
- 先调用
tryRelease()
办法尝试开释以后持有的锁资源。 - 如果胜利开释了锁资源,则调用
unparkSuccessor()
办法去唤醒一个期待锁的线程。
四、总结
到这里ReentrantLock
加锁开释锁的过程曾经学习结束,ReentrantLock
是基于AQS实现的独占式锁,外部保护了一个FIFO队列
实现未获取到锁的线程进行排队工作, ReentrantLock
外部有FairSync
(偏心锁)和NonfairSync
(非偏心锁)两种实现,通过调用lock()
办法加锁,调用unlock()
办法解锁。
五、本人实现一个可重入的独占锁
通过继承AbstractQueuedSynchronizer
类重写tryAcquire()
和tryRelease()
办法实现自定义的可重入独占锁。
代码如下:
public class SyncLock extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int acquires) { // 获取以后线程 final Thread current = Thread.currentThread(); // 获取以后锁状态 int c = getState(); // 如果锁状态为0, 示意以后锁是闲暇的 if (c == 0) { // 调用CAS原子操作设置锁状态 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; } @Override protected 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; }}class AQSTest { public static void newThread(SyncLock syncLock, String name, int time) { new Thread(new Runnable() { @Override public void run() { System.out.println("线程" + Thread.currentThread().getName() + "开始运行, 筹备获取锁。"); syncLock.acquire(1); try { System.out.println("线程" + Thread.currentThread().getName() + ", 在run办法获取了锁。"); lockAgain(); try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } finally { System.out.println("线程"+Thread.currentThread().getName()+" 在run办法中开释了锁。"); syncLock.release(1); } } private void lockAgain() { syncLock.acquire(1); try { System.out.println("线程" + Thread.currentThread().getName() + ", 在lockAgain办法获取了锁。"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } finally { System.out.println("线程"+Thread.currentThread().getName()+" 在lockAgain办法中开释了锁。"); syncLock.release(1); } } }, name).start(); } public static void main(String[] args) { SyncLock syncLock = new SyncLock(); newThread(syncLock, "t1111", 1000); newThread(syncLock, "t2222", 1000); newThread(syncLock, "t3333", 1000); newThread(syncLock, "t4444", 1000); }}
下面代码测试后果如下:
线程t1111开始运行, 筹备获取锁。线程t2222开始运行, 筹备获取锁。线程t1111, 在run办法获取了锁。线程t1111, 在lockAgain办法获取了锁。线程t4444开始运行, 筹备获取锁。线程t3333开始运行, 筹备获取锁。线程t1111 在lockAgain办法中开释了锁。线程t1111 在run办法中开释了锁。线程t2222, 在run办法获取了锁。线程t2222, 在lockAgain办法获取了锁。线程t2222 在lockAgain办法中开释了锁。线程t2222 在run办法中开释了锁。线程t4444, 在run办法获取了锁。线程t4444, 在lockAgain办法获取了锁。线程t4444 在lockAgain办法中开释了锁。线程t4444 在run办法中开释了锁。线程t3333, 在run办法获取了锁。线程t3333, 在lockAgain办法获取了锁。线程t3333 在lockAgain办法中开释了锁。线程t3333 在run办法中开释了锁。
六、ReentrentLock和synchronized的比拟
相同点:
- 都是加锁形式同步
- 都是重入锁。
- 都是通过阻塞的形式实现同步。
不同点
- 原始形成:
synchronized
是java语言的关键字,是原生语法层面的互斥,由JVM实现,而ReentrentLock
是JDK1.5之后提供的API层面的互斥锁。 - 实现:
synchronized
是通过JVM实现加锁解锁,而ReentrentLock
是API层面的加锁解锁,须要手动解锁。 - 代码编写:
synchronized
不须要手动开释锁,润饰办法或者代码块,而ReentrentLock
必须手动开释锁,如果没有开释锁可能造成死锁景象。须要lock()
和unlock()
办法配合try/finally
语句块实现。 - 灵活性:
synchronized
只能用于润饰办法或者代码块,灵活性低,而ReentrentLock
是办法调用能够跨办法,灵活性高。 - 是否期待可中断:
synchronized
不可中断,除非抛出异样,而ReentrentLock
是能够中断的,如果持有锁的线程长期不开释锁,正在期待的线程能够抉择放弃期待,通过设置超时工夫办法。 - 是否偏心锁:
synchronized
是不偏心锁,而ReentrentLock
是可偏心锁也可不偏心锁。 - 实现原理:
synchronized
是通过编译,会在同步代码块前后别离生成monitorenter
和monitorexit
两个指令实现同步,在执行monitorenter
的指令时会尝试获取锁,获取锁胜利会通过计数器+1,执行结束之后会执行monitorexit
执行计数器-1,当计数器为0时开释锁,如果获取锁失败就会进入阻塞状态,而ReentrentLock
是通过CAS + CLH队列
实现,通过CAS
原子性操作实现对锁状态state
的批改,通过CLH队列
实现对未获取到锁的线程进行排队工作。