除了内置锁synchronized以外,还有ReentrantLock和ReentrantReadWriteLock实现防止临界资源的竞争,也就是所谓的编程式锁,内部实现AbstractQueuedSynchronizer,以下简称AQS,AQS也是JUC包中Semaphore,CountDownLatch,CyclicBarrier的基础。
ReentrantLock可以实现资源访问互斥。
ReentrantLock lock = new ReentrantLock();
。。。
try{
lock.lock();
//临界资源
。。。
}finally {
lock.unlock();
}
ReentrantReadWriteLock可实现读读兼容,读写加锁不兼容,写写不兼容。
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock write = lock.writeLock();
Lock read= lock.readLock();
。。。
try{
write.lock();
。。。
}finally {
write.unlock();
}
或
。。。
try{
read.lock();
。。。
}finally {
read.unlock();
}
锁的资源也就是临界资源在lock()和unlock()中,先lock()然后在finally 中unlock(),为什么要这样做?如何实现保证同步 ,与内置锁有哪些优势和区别?
概要
本想从ReentrantLock实现上引出AQS,但是篇幅较大,越写越把AQS落在末尾,读起来十分不顺,最后弄砸了。这里将先从AQS实现介绍,再介绍ReentrantLock和ReentrantReadWriteLock,Semaphore,CountDownLatch实现。
AQS
基本结构
- 结构
AQS继承了抽象类AbstractOwnableSynchronizer
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
AbstractOwnableSynchronizer是一个抽象类接口,无具体实现,
- 属性
AQS有三个属性
private transient volatile Node head;
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
这里state表示同步状态,互斥锁中要求state状态只能由一个线程变更,不能获得锁即意味着是线程需要等待,也就是进入一个CHL队列,head表示队列头部,tail队列尾部。
- Node
以下代码可以看出,Node为双向链表(prev和next表示前,后节点),节点值为Thread ,waitStatus表示状态,还有条件队列nextWaiter。
// waitStatus可取值 0、CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
//条件队列或是共享锁状态,添加队列使用
Node nextWaiter;
waitStatus状态:
- CANCELLED(value:1):因超时或中断造成,是最终态,节点对应线程不在阻塞;
- SIGNAL(value:-1):继任节点是阻塞或者很快变成阻塞,当节点在释放锁或取消状态中,会将继任节点线程从block状态中unpark出来;
- CONDITION(value:-2):条件队列(调用Condition),下一个状态为0;
- PROPAGATE(value:-3):传播共享锁。
功能点
锁分互斥锁、共享锁,互斥锁保证对临界资源访问同一时间只有1个线程,共享锁保证读读不加锁,写读或读写加锁;还有特性要求,要求访问临界资源的请求按照访问先后顺序,进入临界资源(即公平锁),也有要求访问时不按照先后顺序访问(即非公平锁)。
属性state表示临界资源的状态,state改变通常由实现类通过AQS#compareAndSetState去更新,如ReentrantLock中大于0表示有线程持有该状态,等于0表示无线程持有。互斥锁要求AQS实现类实现tryAcquire(尝试更新锁状态,返回值大于0表示该获取线程获得临界资源执行状态,无需排队,小于等于0需要排队执行。公平锁可以返回0,排队执行;非公平锁,线程进入时可尝试更新state获取锁,无需排队
)、tryRelease(释放锁
),共享锁则是tryAcquireShared、tryReleaseShared。
* To use this class as the basis of a synchronizer, redefine the
* following methods, as applicable, by inspecting and/or modifying
* the synchronization state using {@link #getState}, {@link
* #setState} and/or {@link #compareAndSetState}:
*
*
* - {@link #tryAcquire}
*
- {@link #tryRelease}
*
- {@link #tryAcquireShared}
*
- {@link #tryReleaseShared}
*
- {@link #isHeldExclusively}
*
互斥锁实现上,AQS内部实现了方法acquire,acquireInterruptibly,tryAcquireNanos方法(这些方法内部调用tryAcquire,tryAcquire返回true表示获得临界资源执行权限,即获得锁,false表示未获得锁,需要排队执行
),目的是去获取锁,未获得锁时将线程进入等候队列,实现类(如ReentrantLock,ReentrantReadWriteLock)调用这些方法,实现了诸如lock()、lockInterruptibly()、tryLock()、tryLock(long timeout, TimeUnit unit);锁释放,提供release(int arg) 方法给实现类使用,release内部调用了tryRelease。
共享锁实现上,提供了acquireShared(int arg),acquireSharedInterruptibly(int arg),tryAcquireSharedNanos(int arg, long nanosTimeout)方法,内部实现上调用了实现类的tryAcquireShared判断临界资源状态;锁的释放上,有releaseShared(int arg) ,内部调用了实现类的tryReleaseShared。
实现原理
对acquire,acquireInterruptibly,acquireShared等内部方法进行解读,理解AQS的实现原理。
互斥锁
获取锁方法有acquire,tryAcquireNanos,acquireInterruptibly方法,锁释放有release(int arg)。
acquire
调用实现类的tryAcquire,返回值true当前线程获得锁,返回false表示未获得锁线程需要入CHL等候队列排队执行。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
//构造排他节点进入FIFO队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//selfInterrupt()未做任何特殊处理,将线程发生的中断状态传递给外层调用
selfInterrupt();//Thread.currentThread().interrupt();
}
在看addWaiter方法,首先将线程,还有指定互斥模式作为参数构造Node节点,优先用CAS将节点设置为tail节点,如果存在并发出现CAS失败,调用enq入队,enq中自旋方式调用CAS确保节点进入队列尾部。
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
acquireQueued方法中会不断尝试获取锁,先检查前一个节点是否是head节点(head节点表示当前节点在执行或已经执行完毕
),如果是head节点且tryAcquire返回true,表示获取锁成功,将当前节点设置为head节点(同时将pre=null,前节点的next=null
);获取锁失败,会检查是否应该block(前节点是SIGNAL,待执行或在运行,当前线程需要阻塞),如果应该阻塞,调用parkAndCheckInterrupt进行阻塞,并返回线程是否是中断状态。
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//检查线程中断状态
boolean interrupted = false;
for (;;) {
//前一节点
final Node p = node.predecessor();
//如果前节点是头节点,调用tryAcquire()再次尝试为当前线程获得锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire检查节点是否应该block
//parkAndCheckInterrupt中调用方法阻塞线程,返回线程是否是处于中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//属于特殊情况。如果未执行到failed = false时发生异常,这里才会执行。
//cancelAcquire会节点状态变成取消,从队列节点中剔除。
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire方法,前节点为SIGNAL状态,返回true
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//前一节点SIGNAL,返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//如果前面连续节点是因超时或中断变成CANCELLED状态,需要从链表中剔除这些节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//如果节点是初始状态0或PROPAGATE,将pred(前一节点)设置SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
调用LockSupport#park阻塞当前线程,并返回线程是否处于中断状态。
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
acquireInterruptibly
顾名思义,在抢锁过程中有线程中断则不能获取锁,抛出InterruptedException。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
doAcquireInterruptibly大致实现同acquireQueued,一点点区别,返回值是void不是true,还有在获取锁过程中有中断行为则抛出InterruptedException,将节点从队列中删除。
/**
* Acquires in exclusive interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
//发生中断或异常,failed =true,cancelAcquire方法会将
//节点状态变成取消,从队列节点中剔除。
if (failed)
cancelAcquire(node);
}
}
tryAcquireNanos(int arg, long nanosTimeout)
和acquire区别在于:如果线程处于中断状态,tryAcquireNanos中抛出InterruptedException异常,acquire是不会的;acquire中没有Timeout时间限制,在tryAcquireNanos中如果在nanosTimeout时间内未能获得锁,返回false,获取锁流程结束。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())//中断状态,抛出异常
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);//调用doAcquireNanos,进入队列,去获取锁
}
doAcquireNanos方法中先检查nanosTimeout不能小于0,入队列,在nanosTimeout时间内去抢锁,超时则不能获取,在获取锁时发生线程中断会抛出异常不能获取锁;
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;//截止时间
final Node node = addWaiter(Node.EXCLUSIVE);//添加到队列尾部
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
//前节点是head,尝试获取锁
if (p == head && tryAcquire(arg)) {//如果能获取锁,返回true
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
//在deadline 时间后还未能获取锁,超时发生,返回false
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
//检查是否处于阻塞,如果nanosTimeout(剩余时间)大于1000纳秒时,
// 阻塞线程nanosTimeout 个时间
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
//这里除了发生异常时会执行,有线程中断或者超时发生也会调用,
//cancelAcquire说明见acquire
if (failed)
cancelAcquire(node);
}
}
release(int arg)
先检查tryRelease,确认释放锁,将head节点后继节的线程unpark(唤醒),要注意head节点对应线程正在执行或已经执行完毕,这里是锁在获取后释放所以表示的是已经执行。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor唤醒head节点中未执行的后继节点
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//将head节点设置成0,表示已经head节点对应线程已经获取锁完成
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
//如果head后继节点是空或取消状态,从tail往前遍历链表,找到最前面且还未执行的节点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//唤醒节点
if (s != null)
LockSupport.unpark(s.thread);
}
小结
通过tryAcquire方法查询或更新临界资源可用状态,tryRelease将锁状态变更成其他线程可抢夺状态。
竞争情况下,如何保证临界资源访问。
从锁未有线程获取过开始说起,此时CHL队列是空,有线程N,N1,N2,...进入临界区。
- 见下图,线程N,其中N第一个发现队列是空且tryAcquire=true,N获得执行权限,不入队列;在锁未释放时,陆续有线程N1,N2...,需要入队列,第一个进入队列的线程N1,会先构造空的pre节点,构造N1节点,然后第二个节线程N2...入下图,等候线程节点是CHL(双向列表),节点的连接通过CAS保证原子操作,pre和tail使用关键字volatile标识具有可见性。
- 线程N未释放锁期间,仍有线程进入获取锁,如上1图中一样,将进入线程CAS进入队列末尾。
3.线程N释放锁,会唤醒head继任节点N1,通过tryAcquire和新进去的线程抢夺锁。如果新线程获得锁,N1阻塞;N1获得锁,变成head节点,新线程进入队列末尾。如下图(橘色着色部分pre和next指向删除
):
4.后续执行流程见步骤2和3,交替执行。
PS:等待队列中线程是可以取消的,如acquireInterruptibly,tryAcquireNanos,发生中断,将中断执行,从队列中删除该节点;当然tryAcquireNanos方法,在nanosTimeout时间内未获得锁,也会从队列中删除。
以上流程,保证了临界资源同一时间只能被一个线程持有,兼且留有扩展可实现公平锁或非公平锁。
共享锁
共享锁实现有acquireShared(int arg),acquireSharedInterruptibly(int arg),tryAcquireSharedNanos(int arg, long nanosTimeout),锁的释放有releaseShared(int arg)。
acquireShared
无锁,或者是S锁时,tryAcquireShared返回值大于0;有X锁时,小于0。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
doAcquireShared中,先构造SHARED模式node节点入队列(逻辑上和互斥锁的CHL是一样的),如果前节点是head节点,且调用tryAcquireShared大于等于0,获取共享锁成功,将节点设置成head,并释放队列中等待的线程,否则检查节点状态并决定是否阻塞。
private void doAcquireShared(int arg) {
//上文有说,这里不再介绍
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {//头节点才执行
int r = tryAcquireShared(arg);//这里一定要区分共享锁,排他锁
if (r >= 0) {
//将节点node设置为头结点,释放在队列中等待的线程
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();//如果线程是中断,这里将保持中断
failed = false;
return;
}
}
//shouldParkAfterFailedAcquire检查是否应该阻塞
//parkAndCheckInterrupt阻塞线程,检查线程是否中断态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//和acquire实现一样,只有异常时执行,目的是队列中取消节点
cancelAcquire(node);
}
}
设置成头结点,并唤醒处于SIGNAL状态线程,将他们变成PROPAGATE状态。
private void setHeadAndPropagate(Node node, int propagate) {
//记录更新前的头结点
Node h = head; // Record old head for check below
setHead(node);
//tryAcquireShare的值大于0,或者原头结点或现头结点是SIGNAL或PROPAGATE态
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//后继节点是null或是共享锁,执行doReleaseShared
if (s == null || s.isShared())
//head不变一个期间,唤醒SIGNAL状态的线程,将状态变更为
doReleaseShared();
}
}
doReleaseShared方法中,开启轮询(终止条件是head节点在一次操作期间不变,head具有可见性,使用h == head可检查):
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//将head状态由SIGNAL变成0
//CAS成功执行unparkSuccessor唤醒head继任节点的线程
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);//唤醒head继任节点的线程
}
//0变成PROPAGATE状态
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
acquireSharedInterruptibly(int arg)
在获取锁前检查线程状态,如果存在中断行为,InterruptedException发生;如果tryAcquireShared小于0,执行doAcquireSharedInterruptibly获取锁:如果获锁成功,唤醒队列中SIGNAL状态的线程;获取锁不成功,检查阻塞状态,决定是否park线程,如果线程发生中断,则InterruptedException发生。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
和doAcquireShared方法逻辑上大致相同,不同的在获取锁之前如果发生中断行为,直接抛出InterruptedException异常,将节点从队列中删除。
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryAcquireSharedNanos(int arg, long nanosTimeout)
看看下面的代码,是不是很熟悉,简直是复制/粘贴,这里简单介绍下,
- 线程有中断行为,不能获取锁,异常InterruptedException发生
- tryAcquireShared大于等于0,表示获取锁;
- 小于0,有互斥锁在执行,调用doAcquireSharedNanos:
doAcquireSharedNanos在nanosTimeout时间内获取锁,超时则退出,获取锁后逻辑也很熟悉,这里不再介绍;再获取锁过程中如有中断行为发生,异常InterruptedException抛出,节点从队列中删除;
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
releaseShared(int arg)
看下代码,releaseShared变更状态成功,执行doReleaseShared(),在head不变一个期间内,唤醒SIGNAL状态的线程,将状态变更为PROPAGATE
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
小结下
S表示共享锁,X表示排它锁。共享锁机制表现在存在S、S兼容,X、S不兼容,X、S不兼容,这些行为由实现类的tryAcquireShared(arg)检查正在执行的线程是Share,EXCLUSIVE模式的,在根据state决定是否可以共享还是应该排队等待。
具体实现可以看看Semaphore,CountDownLatch,ReentrantReadWriteLock的readerLock。
实现类
互斥类ReentrantLock,存在S,X锁的ReentrantReadWriteLock,基于共享锁实现的Semaphore和CountDownLatch。
ReentrantLock实现
结构
ReentrantLock实现Lock接口类
public class ReentrantLock implements Lock, java.io.Serializable {
Lock接口类中有如下方法:
属性
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
//AbstractOwnableSynchronizer内存在
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
Sync 继承AQS(AbstractQueuedSynchronizer),在内部类中子类有非公平锁NonfairSync、公平锁FairSync。
构造函数
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//fair=true,公平锁;false,非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock及unlock等方法如下,具体实现在sync及其实现类NonfairSync、FairSync中。
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
Sync 实现如下
实现了非公平锁获得,以及锁的释放,以及其他方法。这里AQS中state这里有具体含义,0表示可以获取锁,不等于0表示有线程持有锁。
tryLock() 方法调用这里nonfairTryAcquire,锁的释放调用AQS的release方法,锁的获取NonfairSync,FairSync中。
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
//尝试获得临界资源的执行权,下称锁(含义同内置锁中持有锁对象,
//表示只有获得权限的线程在执行,除非释放权限,否则其他线程将等待),
//这里返回true表示获得锁;false表示有别线程已经获得锁了;
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//锁状态由AQS中state表示,具体值由实现类定义
//在ReentrantLock中state=0表示,无线程占有;state>0表示已经有线程获得执行权
int c = getState();
if (c == 0) {
//CAS更改state的值
if (compareAndSetState(0, acquires)) {
//设置exclusiveOwnerThread属性
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//当前线程获得执行权限
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//需保证获得state状态的线程和当前线程是一样的
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
//设置null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//当前线程是否获得执行权
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//这里指同步状态state的值
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//如上表述,state>=0,非0表示已经有线程锁定
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
NonfairSync
如果state状态是0,利用CAS尝试将状态变成1,表示线程获得锁,否则调用AQS#acquire。这样实现了锁的获得不一定按照先进先获取,在进入临界区也可以先获得锁。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//这里先争夺锁:利用CAS尝试将state从0变成1,如果成功则当前线程获得执行权;
//不成功说明有别的线程获得锁,则排队。所以这里不是先进先出这种公平方式来获得锁的
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//调用AQS#acquire方法进行排队
}
protected final boolean tryAcquire(int acquires) {
//调用Sync的nonfairTryAcquire方法 尝试获得锁,
//有其他线程持有锁,立即返回false,不排队。
return nonfairTryAcquire(acquires);
}
}
FairSync
锁状态检查及变更由tryAcquire控制,lock方法调用AQS#acquire,实现先进入先获得锁,即是公平锁。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//入队,锁执行权限从队列第一开始,到第二个,第三个.....依次获得锁权限
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
//无线程执行,且队列中无排队线程,CAS将状态变成已被当前线程占有
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;
}
}
ReentrantReadWriteLock
结构
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
readlock读锁,即S锁;writerLock写锁,即X锁。Sync 同ReentrantLock的属性Sync 一样,继承AbstractQueuedSynchronizer,有NonfairSync和FairSync。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
如果队列中有任务,writerShouldBlock,readerShouldBlock返回true,线程需要进入队列排队执行。
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
AQS中说过,实现类实现tryAcquire,tryRelease,tryAcquireShared,tryReleaseShared(int unused)方法,是使用公平锁还是非公平锁,锁的状态变更。ReentrantReadWriteLock中有ReadLock和WriteLock,如何实现读读兼容,读写不兼容,写读不兼容,看看ryAcquire等实现就可以知道了。
- WriteLock是互斥锁,锁的获取调用AQS#acquire,acquireInterruptibly,tryAcquireNanos,释放调用release,锁状态的控制由tryAcquire,tryRelease决定,这里要重点介绍。
public static class ReadLock implements Lock, java.io.Serializable {
public void lock() {
sync.acquire(1);//AQS#acquire
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//AQS#acquireInterruptibly
}
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
//AQS#tryAcquireNanos
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
。。。
}
- ReadLock是共享锁,锁的获取调用AQS的acquireShared,acquireSharedInterruptibly,tryAcquireSharedNanos,释放调用releaseShared,锁状态的控制由tryAcquireShared,tryReleaseShared决定,也是这里要重点介绍的。
public static class ReadLock implements Lock, java.io.Serializable {
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);
}
看看Sync中tryAcquire,tryRelease,tryAcquireShared,tryReleaseShared实现
互斥锁
tryAcquire中同步状态state不等于0表示线程持有,等于0表示可以去获取锁,Walkthrough如下:
- 检查同步状态,如果不等于0,意味着有线程持有状态。如果线程是自身线程,支持可重入,state有限制的,在0到(2^16-1)位上,最大值是65535,超过则Error发生,执行 setState(c + acquires)累加,返回true;其他线程,返回false,进入等待队列
- state=0,可以去获得锁。writerShouldBlock()=true,也会返回false:非公平锁中返回true,或公平锁中如果等候队列是空链表。如果有竞态线程且CAS成功获得锁,当前线程CAS失败返回false。
- 如果当前线程CAS锁状态成功,设置互斥线程为当前线程。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//w==0表示只有ReadLock存在,无互斥发生
//w!=0发生互斥,current != getExclusiveOwnerThread() 表示当前线程不是重入的
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
释放锁,tryRelease使用在互斥锁中,如state - releases=0,表示当前线程重入所有地方已经全部执行完毕,需要将exclusiveOwnerThread为null,CAS将锁状态state变更成0,以便于其他线程获得。
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
共享锁
tryAcquireShared,tryReleaseShared实现。
tryAcquireShared流程如下:
- 如果互斥锁被其他线程持有,返回-1,排队等待互斥锁释放;
- 如果读锁不被阻塞,共享锁线程数小于65535,CAS更新共享锁计数(高位更新)成功,更新读锁计数;
- 1和2外,线程应该被阻塞或更新共享锁状态时因竞争失败,执行fullTryAcquireShared,该方法重复1和2。
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
//锁状态
int c = getState();
//exclusiveCount确保锁状态最大为65535,
//这里允许先获得互斥锁,后获得共享锁,如果是其他线程这里返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//正在执行共享锁计数,这里使用高位(即2^16位以上)表示
int r = sharedCount(c);
//在公平锁,如果队列有元素,readerShouldBlock()=true,
//表示先进入队列,按照FIFO方式获取锁;非公平锁中,
//如果有互斥锁存在,不能马上获得锁,readerShouldBlock()=true
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {//2^16~2^32上表示共享锁状态
if (r == 0) {//设置第一个执行的线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;//重入线程,计数加1
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;//统计持有共享锁数量
}
return 1;
}
return fullTryAcquireShared(current);
}
释放锁,锁状态减SHARED_UNIT,共享锁计数减1。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
ReentrantReadWriteLock小结
锁state低位0~(216-1)上表示互斥状态(只有1个线程占有,同一个线程可重复,state将acquires累加);216-2^32上表示共享锁行为,acquires累加(可以是不同线程,自身线程可重入)。
WriteLock基于互斥实现,也就是AQS#acquire,release实现,有readLock或其他线程获得锁会排队等候释放锁,tryAcquire中确保获取锁时只有state=0,CAS从0变成acquires才能获得锁,当然自身线程是已经获得互斥锁,再次重入也没问题。
ReadLock共享锁,基于AQS#acquireShared,releaseShared实现,有WriteLock存在时需要排队等候互斥锁的释放才能抢锁,支持重入。
如果线程先WriteLock#lock,在ReadLock#lock,是可以的,执行顺序上WriteLock#lock->ReadLock#lock ->ReadLock#unLock ->WriteLock#unLock。这种行为被人称为
锁降级
。先ReadLock#lock,在WriteLock#lock,会发生什么?从tryAcquire代码中可以看到,只有state等于0且CAS成功获得锁或者自身已经获得互斥锁,程序才能继续往下执行,但自身线程ReadLock#lock获得锁,此时state!=0,WriteLock#lock会将线程进入等候队列等候state=0,WriteLock一直等不到state变成0饿死,程序无法继续执行。这种行为也就是所谓的
锁升级
了
Semaphore实现
信号量,实际使用场景还挺多,比如限流中并发数限制场景。
设置许可数量permits,通过以下方法操作
acquire()
内部实现为AQS#acquireSharedInterruptibly。获取许可,permits-1;如果permits=0时,acquire方法会将调用线程排队进入等待。
acquire(int permits)
同acquire(),获得许可前提是总许可数量>参数permits
release()
程序执行完成后,释放许可,许可数量=permits+1
release(int permits)
逻辑同release(),许可数量=总许可数量+参数permits
核心为tryAcquireShared实现,调用Semaphore(int permits) 初始化时即是将锁state设置成permits。
看下公平和非公平下实现
nonfairTryAcquireShared,非公平下,CAS在同步状态state大于acquires,设置state=state-acquires成功,不管线程是先进入还是后进入的即是获得许可。
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平下,hasQueuedPredecessors()=true表示等候队列有线程节点,需入队尾,保证是按照最先进来的线程先获得许可。
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
CountDownLatch实现
闭锁,含义是等待一件事或一堆事完成后才去做,如WSRS中,和目的端通信,可重试3次直到收到应答,如果3次没有收到应答或者超过3秒,就会到其他流程,如下。
重试3次流程:
CountDownLatch nums = new CountDownLatch(3);
do{
Thread t = new Thread (new T1());
t.start();
t.join(1000);
}while(通信成功 || nums.await(3,TimeUnit.SECONDS))
其他流程
通信线程:
T1 implements Runnable{
public void run(){
和endPoint通信
if(通信失败){
nums.countDown();
}
}
}
CountDownLatch方法:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
可以看出基于AQS#acquireSharedInterruptibly,releaseShared,tryAcquireSharedNanos方法,是S锁。
核心还是在CountDownLatch#Sync上,定义了tryAcquireShared和tryReleaseShared实现。
下面代码,业务方调用CountDownLatch#await()时,如果锁状态state(也就是Sync中count)等于0,意味着业务方获得S锁可以执行业务代码,state不等于0,业务方线程排队等待state=0或超时。
CountDownLatch#countDown()将使state=state-1,直到state=0。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
AQS与synchronized相比
AQS是编程式锁,与synchronized区别还是很大的:
- 原理:synchronized基于JVM原语;AQS编程式锁,双向链表+同步状态+CAS+LockSupport实现;
- 使用上,synchronized使用比较简明,使用关键字synchronized锁定资源和{}锁定执行范围;AQS,要对临界资源和范围显示加锁和解锁,缺一不可,使用上稍稍复杂,但胜在灵活可控;
- 特性上,synchronized只能支持非公平锁,不支持公平锁,对等待线程无法中断执行和设置超时时间;相反,AQS能实现公平锁和非公平锁,也支持中断等待线程,以及设置超时时间去获取锁。
需要注意的是获得锁的线程,AQS同synchronized一样的,无法取消执行的
; - 负载上:synchronized在1.5,1.6以来做了很多优化,诸如:无锁化,锁粗化,锁自旋,轻量级锁,偏向锁等,性能上和AQS相差无几;但在并发冲突高场景下,synchronized抢锁频繁,CPU消耗大波动大,AQS表现平稳。