一、AbstractQueuedSynchronizer简介

AQS(AbstractQueuedSynchronizer)是并发容器JUC(java.util.concurrent)下locks包内的一个抽象类,是一个同步器,是用来构建锁或者其他同步组件的基础框架,内部维护了一个成员变量state表示同步状态,state=0表示线程未获取到锁,state > 0表示获取到锁,state > 1表示重入锁的数量,被 volatile修饰保证了可见性,通过CAS操作对其修改,内置维护了FIFO队列实现对未获取到锁的线程进行排队工作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZJXLavYo-1648444823734)(https://qtspace.cn/contentimg/20.jpg)]

二、AbstractQueuedSynchronizer源码解析

核心成员变量

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizerimplements 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主要有三个核心成员变量stateheadtail

  • 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实现的,比ReentrantLockCountDownLatchCyclicBarrierSemaphore等,有的只支持独占锁,如ReentrantLock#lock(),有的支持共享锁,如Semaphore,从前文的Node类的定义也能看到

/*** 节点正在共享模式下等待的标记*/
static final Node SHARED = new Node();/*** 节点正在独占模式下等待的标记*/
static final Node EXCLUSIVE = null;

AQS实现了两套加锁解锁的方式,那就是独占锁共享锁。我们就从AQS最常用的类ReentrantLock来学习AQS的核心方法。

三、ReentrantLock

简介

ReentrantLock是基础AQS实现的一个可重入且独占式锁。内置了一个Sync同步器类实现了AQS,且支持公平锁和非公平锁,其实现类分别是FairSyncNonfairSync

ReentrantLock所有操作都是通过核心内部类Sync操作,由子类FairSyncNonfairSync实现。

private final Sync sync;

ReentrantLock加锁过程

lock

lock()就是加锁,该方法定义如下:

public void lock() {sync.lock();
}

FairSyncNonfairSync具体实现:

// FairSync实现
final void lock() {acquire(1);
}// NofairSync实现 setExclusiveOwnerThread是父类AQS
final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(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;}}// 快速添加失败, 则调用enqenq(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方法主要流程如下:

  1. 通过for(;;)死循环自旋,直到node(当前)节点获取到锁。
  2. 获取当前节点的前一个节点p。
  3. 如果节点p是头节点,然后调用tryAcquire()尝试获取锁,如果获取成功就将node节点设置成头节点然后返回。
  4. 如果节点p不是投节点或者获取锁失败,调用shouldParkAfterFaildAcquired()方法来决定是否要阻塞当前线程。
  5. 如果要阻塞当前线程,调用parkAndCheckInterrupt()方法阻塞当前线程。
  6. 如果当前线程发生异常,非正常退出,调用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()方法主要流程如下:

  1. 如果前一节点状态是Node.SIGNAL,则直接返回true当前线程进入阻塞状态。
  2. 如果前一节点状态是Node.CANCELLED(大于0就是CANCELLED),表示前一个节点已经被唤醒了,要从队列中移动CANCELLED状态的节点,所以送pred节点一直向前查询不是CANCELLED状态的节点,并将该节点赋值成当前节点的前一节点,表示当前节点的前一节点发生变化,在acquireQueued()方法中进行下一次循环。
  3. 不是前面两种状态,只能是0或者PROPAGATE状态,修改前一节点的状态为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为nullcompareAndSetNext(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}
}
加锁过程总结

  1. 首先调用lock()方法,这个方法有两个子类FairSyncNofairSync实现,表示公平锁和非公平锁,两个类的不同就是NofairSync会直接调用compareAndSetStaus()方法修改加锁状态,如果成功当前线程获取到锁。
  2. 然后调用父类AbstractQueuedSynchronizedacquire()方法获取锁。
  3. acquire()方法调用tryAcquire()方法尝试获取锁,tryAcquire()由子类FairSyncNofairSync实现分别调用fairTryAcquire()nonfairTryAcquire()方法尝试获取锁。这两个方法里面实现了重入锁的逻辑,如果当前锁状态是未获取到锁,则调用CAS设置锁状态,如果是获取到锁状态则会判断获取锁的线程是否是当前线程,如果是则是重入锁的逻辑记录当前线程获取锁的次数。
  4. 如果tryAcquire()方法调用获取锁失败,则会调用acquireQueued()方法再获取锁或者进入阻塞状态,acquireQueued()方法首先调用了addWaiter()方法用于将当前线程封装成一个节点加入队列队尾,然后再调用acquireQueued()方法获取锁或者进入阻塞状态,acquireQueued()方法会通过自旋的方式根绝当前节点状态判断是否进入阻塞状态。当别的线程释放锁的时候,可能唤醒这个线程,再调用tryAcquire()方法获取锁。
  5. 如果发生异常,将当前节点状态设置成CANCELLED。

ReentrantLock释放锁过程

unlock

调用unlock()方法释放锁,然后调用release()方法,源码如下:

public void unlock() {sync.release(1);
}
release

releaseAbstactQueuedSynchronized定义的方法用于释放锁,源码如下:

/*** 在独占锁模式下, 释放锁*/
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()释放锁流程如下:

  1. 调用tryRelease()方法尝试释放锁,返回true表示释放锁成功,返回false表示还持有锁资源。
  2. 如果释放锁成功了,且头节点不为null,就要唤醒被阻塞的线程,调用unparkSuccessor()方法唤醒一个等待的线程。
tryRelease

tryRelease尝试释放锁方法是有子类实现的,下面是ReentrantLockSynctryRelease()方法实现:

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;// 获取独占锁的线程置为nullsetExclusiveOwnerThread(null);}// 锁状态设置成未锁定setState(c);return free;
}

tryRelease()尝试释放锁流程如下:

  1. 首先获取新的锁状态
  2. 判断当前线程是否是获取独占锁的线程,如果不是抛异常。
  3. 如果新的锁状态是未锁定状态,获取独占锁的线程置为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节点的下一个非取消状态的节点所在线程流程如下:

  1. 先将node节点的状态设置为0。
  2. 寻找下一个状态不为取消的节点s。
  3. 如果节点s不为null,调用LockSupport.unpark()方法唤醒s所在线程。
释放锁过程总结

  1. 先调用tryRelease()方法尝试释放当前持有的锁资源。
  2. 如果成功释放了锁资源,则调用unparkSuccessor()方法去唤醒一个等待锁的线程。

四、总结

到这里ReentrantLock加锁释放锁的过程已经学习完毕,ReentrantLock是基于AQS实现的独占式锁,内部维护了一个FIFO队列实现未获取到锁的线程进行排队工作, ReentrantLock内部有FairSync(公平锁)和NonfairSync(非公平锁)两种实现,通过调用lock()方法加锁,调用unlock()方法解锁。

五、自己实现一个可重入的独占锁

通过继承AbstractQueuedSynchronizer类重写tryAcquire()tryRelease()方法实现自定义的可重入独占锁。

代码如下:

public class SyncLock extends AbstractQueuedSynchronizer {@Overrideprotected 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;}@Overrideprotected 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() {@Overridepublic 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是通过编译,会在同步代码块前后分别生成monitorentermonitorexit两个指令实现同步,在执行monitorenter的指令时会尝试获取锁,获取锁成功会通过计数器+1,执行完毕之后会执行monitorexit执行计数器-1,当计数器为0时释放锁,如果获取锁失败就会进入阻塞状态,而ReentrentLock是通过CAS + CLH队列实现,通过CAS原子性操作实现对锁状态state的修改,通过CLH队列实现对未获取到锁的线程进行排队工作。

Java锁(二):AbstractQueuedSynchronizer、ReentrantLock详解相关推荐

  1. java的reentrantlock_JAVA中ReentrantLock详解(转)

    转自https://www.cnblogs.com/java-learner/p/9651675.html 前言:本文解决的问题 RentrantLock与Synchronized区别 Reentra ...

  2. Java并发编程之CyclicBarrier详解

    简介 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生.栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行.闭锁用于等待事件,而栅栏用于等待其他线程. CyclicBarrier ...

  3. 【职坐标】java面向对象三大特性(二)——继承详解

    [职坐标]java面向对象三大特性(二)--继承详解 a) 什么是继承? i. 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可 b) ...

  4. java request获取域,Java Web - Servlet(13)HttpServletRequest详解(获取请求数据、请求分派、请求域)(二)...

    Java Web -- Servlet(13)HttpServletRequest详解(获取请求数据.请求分派.请求域)(2) HttpServletRequest ----------------- ...

  5. Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3

    Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3 总览 问题 详解 String.intern()的作用 link LeetCode的Two Sum题 ...

  6. java线程池ThreadPoolExecutor类详解

    线程池有哪些状态 1. RUNNING:  接收新的任务,且执行等待队列中的任务 Accept new tasks and process queued tasks  2. SHUTDOWN: 不接收 ...

  7. 40000+字超强总结?阿里P8把Java全栈知识体系详解整理成这份PDF

    40000 +字长文总结,已将此文整理成PDF文档了,需要的见文后下载获取方式. 全栈知识体系总览 Java入门与进阶面向对象与Java基础 Java 基础 - 面向对象 Java 基础 - 知识点 ...

  8. Java并发编程最佳实例详解系列

    Java并发编程最佳实例详解系列: Java并发编程(一)线程定义.状态和属性 Java并发编程(一)线程定义.状态和属性 线程是指程序在执行过程中,能够执行程序代码的一个执行单元.在java语言中, ...

  9. Java Thread的interrupt方法详解

    Java Thread的interrupt方法详解 一.概述 interrupt方法的目的是给线程发出中断信号,但是不保证线程真的会中断 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应 ...

  10. Java多线程之volatile详解

    Java多线程之volatile详解 目录: 什么是volatile? JMM内存模型之可见性 volatile三大特性之一:保证可见性 volatile三大特性之二:不保证原子性 volatile三 ...

最新文章

  1. MySQL_数据库操作语句
  2. 记录一下从标定模型中读取参数
  3. linux screen 配置(下标高亮)
  4. 倒计时 2 天 | 神策 2019 数据驱动大会即将开幕
  5. MySQL子查询优化思路
  6. html页面源码_整合SpringMVC之错误处理底层原理及源码分析
  7. function “printf“ declared implicitly
  8. iOS 开发之 - iOS6适配 - 导航栏按钮透明方法
  9. python远程控制终端数据_python 网络远程控制
  10. [转] new 和delete
  11. netsh 禁用端口
  12. Angualr 输入文本框监听enter回车键和esc键方法使用(附常用的键盘事件对应的键码)
  13. 【HDOJ6957】Maximal submatrix(单调栈,最大子矩阵面积)
  14. ctb伺服驱动器说明书_伺服驱动器 CTB BK Servo
  15. ABAQUS混凝土CDP插件,一键生成混凝土CDP本构曲线
  16. 网易云音乐 linux x32,网易云音乐 Win10 UWP 正式变为 Win32 转制版
  17. 安川服务器输入输出信号,谈谈自动控制系统的输入输出信号
  18. 用震盘实现中性笔这一大类笔的笔帽的定向上料设计(SolidWorks模型讲解)
  19. java编写车类_用Java程序创建一个汽车接口,接口中要定义汽车应有的属性和行为,随后编写多个汽车接口的实现类,...
  20. html5 promise,从HTML5与PromiseA+规范来看事件循环

热门文章

  1. mac下配置java运行环境
  2. 端游吃鸡测试服服务器维护,《绝地求生大逃杀》PC版今日服务器更新维护 上线测试服内容!...
  3. mac 无法启动 无苹果_Mac无法让您发挥创意! 那么,为什么艺术家真的爱苹果?...
  4. 一图看懂 aiohttp 模块:基于 asyncio 的异步HTTP网络库, 资料整理+笔记(大全)
  5. 18计科专业《Java程序设计》教学大纲
  6. 酷播云二维码美化添加自己logo图标的操作教程(原创)
  7. 语音合成(TTS)论文优选:Location-Relative Attention Mechanisms for Robust Long-Form Speech Synthesis
  8. 新闻稿格式以及新闻稿写作须知
  9. 主机与台式计算机的关系,迷你主机和笔记本电脑、台式机的区别
  10. 巨量广告定向系统推荐和莱卡谁更能跑大量