一、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 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是父类AQS
final 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()
方法中进行下一次循环。 - 不是前面两种状态,只能是
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为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队列
实现对未获取到锁的线程进行排队工作。