static final class Node {
// 共享
static final Node SHARED = new Node();
// 独占
static final Node EXCLUSIVE = null;
/**
* 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态
*/
static final int CANCELLED = 1;
/**
* 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
*/
static final int SIGNAL = -1;
/**
* 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,该节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
*/
static final int CONDITION = -2;
/**
* 表示下一次共享式同步状态获取,将会无条件地传播下去
*/
static final int PROPAGATE = -3;
/** 等待状态 */
volatile int waitStatus;
/** 前驱节点,当节点添加到同步队列时被设置(尾部添加) */
volatile Node prev;
/** 后继节点 */
volatile Node next;
/** 等待队列中的后续节点。如果当前节点是共享的,那么字段将是一个 SHARED 常量,也就是说节点类型(独占和共享)和等待队列中的后续节点共用同一个字段 */
Node nextWaiter;
/** 获取同步状态的线程 */
volatile Thread thread;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
复制代码
说明:
1、waitStatus字段:等待状态,用来控制线程的阻塞和唤醒,有5种状态:INITAL/CANCELLED/SINGAL/CONDITION/PROPAGATE。
2、thread字段:Node节点对应的线程Thread。
3、nextWaiter字段:Node节点获取同步状态的模型。tryAcquire(int args)和tryAcquireShared(int args)方法,分别为独占式和共享式获取同步状态。在获取失败时, 它们都会调用addWaiter(Node mode)方法入队。而nextWaiter就是用来表示哪种模式: SHARED:枚举共享模式,EXCLUSIVE:枚举独占模式。
4、predecessor()方法:获取Node节点的前一个Node节点。在方法内部,Node p = prev的本地拷贝是为了避免并发情况下,prev判断完==null时,恰好被修改,从而保证线程安全。
实际上,入队逻辑实现的addWaiter(Node)方法,需要考虑并发情况。它通过CAS方式,来保证正确的添加Node。代码如下:
1: private Node addWaiter(Node mode) {
2: // 新建节点
3: Node node = new Node(Thread.currentThread(), mode);
4: // 记录原尾节点
5: Node pred = tail;
6: // 快速尝试,添加新节点为尾节点
7: if (pred != null) {
8: // 设置新 Node 节点的尾节点为原尾节点
9: node.prev = pred;
10: // CAS 设置新的尾节点
11: if (compareAndSetTail(pred, node)) {
12: // 成功,原尾节点的下一个节点为新节点
13: pred.next = node;
14: return node;
15: }
16: }
17: // 失败,多次尝试,直到成功
18: enq(node);
19: return node;
20: }
复制代码
出队:CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的下一个节点(Node.next)。而后继节点将会在获取同步状态成功时,将自己设置为首节点(head)。 这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可。注意:在这个过程中是不需要使用CAS来保证的,因为只有一个线程,能够成功获取到同步状态。 setHead(Node node)方法,实现上述的出队逻辑,如下图所示:
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
复制代码
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
1、tryAcquire:尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。
2、addWaiter:如果tryAcquire返回false(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部。
3、acquireQueued:当前线程会根据公平性原则来进行自旋,直至获取锁为止。
4、selfInterrupt:产生一个中断
5、下面看下acquireQueued方法:这个方法为一个自旋的过程,当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自省的观察,当条件满足获取到同步状态后,就可以退出自旋的过程。从下面代码中可以看到,当前线程会一直尝试获取同步状态,当然前提是只有其前驱节点为头结点才能够尝试获取同步状态,理由:保持FIFO同步队列原则。头节点释放同步状态后,将会唤醒其后继节点,后继节点被唤醒后需要检查自己是否为头节点。
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; // help GC
failed = false;
return interrupted;
}
//获取失败,线程等待--具体后面介绍
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
还有独占式获取响应中断和独占式超时获取的方法,这里就不详细描述了。
独占式释放同步状态
当线程获取同步状态后,执行完相应逻辑后就需要释放同步状态。AQS提供了release(int arg)方法释放同步状态:
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(int arg)方法来释放同步状态,释放成功后,会调用unparkSuccessor(Node node)方法唤醒后继节点。
总结:在AQS内部维护了一个FIFO同步队列,当线程获取同步状态失败后,则会加入到这个CLH队列的队尾并一直保持着自旋。在CLH同步队列中的线程在自旋时会判断其前驱节点是否为首节点, 如果是首节点就不断尝试获取同步状态,获取成功就退出CLH同步队列。当线程执行完逻辑后,会释放同步状态,释放后会唤醒其后继节点。
2、共享式
共享式与独占式最主要区别在于同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个线程获取同步状态。例如读操作可以多个线程同时读,写操作同一时刻只能有一个线程写。
共享式获取同步状态
AQS提供acquireShared(int arg)方法共享式获取同步状态:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
//获取失败,自旋获取同步状态
doAcquireShared(arg);
}
复制代码
上述方法是先调用tryAcquireShared(int arg)方法尝试获取同步状态,获取失败就调用doAcquireShared(int arg)自旋获取同步状态。
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) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
addWaiter(Node.SHARED)这里先把当前线程加入CLH同步队列的队尾,然后循环(自旋)尝试获取同步状态:node.predecessor()表示当前节点的前驱结点,if (p ==head)如果前驱结点 是首节点的话,则调用tryAcquireShared(int args)方法尝试获取同步状态,获取成功(r >=0)就退出循环(自旋),在退出前唤醒下一个等待的节点(也就是设置下一个节点的前驱节点为首节点)。
共享式释放同步状态
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
复制代码
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
复制代码
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点
int ws = pred.waitStatus;
//状态为signal,表示当前线程处于等待状态,直接放回true
if (ws == Node.SIGNAL)
return true;
//前驱节点状态 > 0 ,则为Cancelled,表明该节点已经超时或者被中断了,需要从同步队列中取消
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
}
//前驱节点状态为Condition、propagate
else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
复制代码
这段代码主要检查当前线程是否需要被阻塞,具体规则如下:
1、如果当前线程的前驱节点状态为SINNAL,表示当前线程需要被阻塞,调用unpark()方法唤醒,直接返回true,当前线程阻塞。
2、如果当前线程的前驱节点状态为CANCELLED(ws >0),则表示该线程的前驱节点已经等待超时或者被中断了,则需要从CLH队列中将该前驱节点删除掉,直到回溯到前驱节点状态<=0,返回false。
3、如果前驱节点非SINNAL,非CANCELLED,则通过CAS的方式将其前驱节点设置为SINNAL,返回false。如果shouldParkAfterFailedAcquire(Node pre, Node node)方法返回false,则调用parkAndCheckInterrupt()方法阻塞当前线程。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
复制代码
parkAndCheckInterrupt()方法主要是把当前线程挂起,从而阻塞住线程的调用栈,同时返回当前线程的中断状态。其内部则是调用LOckSupport工具类的park()方法来阻塞。
public void lock() {
sync.lock();
}
复制代码
Sync为ReentrantLock里的一个内部类,它继承AQS,它有两个子类:公平锁FairSync和非公平锁NonFairSync。ReenTrantLock里面大部分的功能都是委托给Sync来实现的,同时Sync 定义了lock()抽象方法由其子类来实现,默认实现了nonfairTryAcquire(int acquires)方法。 我们来看非公平锁的lock方法
final void lock() {
//尝试获取锁
if (compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获取失败,调用AQS的acquire(int arg)方法
acquire(1);
}
复制代码
首先尝试获取锁,如果获取成功,设置锁被当前线程独占。如果获取失败,则调用acquire(1),该方法定义在AQS中,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
这里先调用tryAcquire(int arg),在AQS中讲过,这个方法需要同步组件自己实现。在NonfairSync中的实现见下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取同步状态
int c = getState();
//state==0 表示该锁处于空闲状态
if (c == 0) {
//用CAS方式占用该锁
if (compareAndSetState(0, acquires)) {
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;
}
复制代码
2、释放锁unlock方法
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//减掉releases
int c = getState() - releases;
//如果释放的不是持有锁的线程,抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//state == 0 表示已经释放完全了,其他线程可以获取同步状态了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//重新设置同步状态的值
setState(c);
return free;
}
复制代码
unlock()内部使用Sync的release(int arg)释放锁,release(int arg)是在AQS中定义的。tryRelease也是需要同步组件自己实现。如果释放成功,再判断首节点后面还有等待同步状态的线程,则调用unparkSuccessor(Node node)方法唤醒下一个线程。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
下面这块就是将state一分为二,高16位用于共享模式,低16位用于独占模式。
*/
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** 取c的高16位值,代表读锁的获取次数 */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** 取c的低16位值,代表写锁的冲入次数,因为写锁是独占模式 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
/**
这个静态内部类的实例用来记录每个线程持有的读锁数量(读锁重入)
*/
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
/**
ThreadLocal的子类
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
组合使用上面两个类,用一个ThreadLocal来记录当前线程持有的读锁数量
*/
private transient ThreadLocalHoldCounter readHolds;
/**
用于缓存,记录“最后一个获取读锁的线程”的读锁重入次数,所以不管哪个线程获取到读锁后,就把这个值占用,这样
就不用到ThreadLocal中查询map了。在获取-释放读锁的这段时间,如果没有其他线程获取读锁的话,此缓存可以帮助提高性能。
*/
private transient HoldCounter cachedHoldCounter;
/**
第一个获取读锁的线程(并且其未获取读锁),以及它持有的读锁数量。
*/
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
//初始化readHolds这个ThreadLocal属性
readHolds = new ThreadLocalHoldCounter();
//为了保证readHolds内存可见性
setState(getState()); // ensures visibility of readHolds
}
复制代码
ReentrantReadWriteLock与ReentrantLock一样,其锁主体依然是Sync,它的读锁、写锁都是依靠Sync来实现的,所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁 和写入锁的方式上不一样。它的读写锁其实就是两个类ReadLock和WriteLock。在ReentrantLock中使用一个int类型的state来表示同步状态,该值表示锁被一个线程重复获取的次数。但是读写锁ReentrantReadWriteLock内部维护着一对锁,需要用一个变量维护多种状态,所以读写锁采用“按位切割使用”的方式来维护这个变量,将其切分为两部分,高16位表示读,低16位表示写。分割之后,通过位运算确定读锁和写锁的状态。假如当前同步状态为S,那么写状态=S & 0x0000FFFF(将高16位抹去),读状态=S >>>16(无符号补0右移16位)。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//当前锁个数
int c = getState();
//写锁
int w = exclusiveCount(c);
if (c != 0) {
// c !=0 && w == 0表示存在读锁,当前线程不是已经获取写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超出最大范围
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 尝试获取写锁
setState(c + acquires);
return true;
}
//是否需要阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置获取锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
复制代码
protected final boolean tryRelease(int releases) {
//如果释放的线程不是锁的持有者,抛异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//同步状态更新
int nextc = getState() - releases;
//若写锁的新线程数为0,则将锁的持有者设置为null
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
复制代码
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
//当前线程
Thread current = Thread.currentThread();
//锁的个数
int c = getState();
//计算写锁,如果存在写锁且锁的持有者不是当前线程,直接返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//计算读锁
int r = sharedCount(c);
//readerShouldBlock:读锁是否需要等待(公平锁原则),且小于最大线程数,且CAS设置读取锁状态成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
如果锁没有被任何线程获取,那么当前线程就是第一个获取读锁的线程
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
}
//如果获取读锁的线程为第一次获取读锁的线程,则 firstReaderHoldCount重入数+1
else if (firstReader == current) {
firstReaderHoldCount++;
}
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);
}
复制代码
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
//当前线程
Thread current = Thread.currentThread();
//如果想要释放锁的线程为第一个获取锁的线程
if (firstReader == current) {
// 仅获取了一次,则需要将firstReader设置为Null,否则-1
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
}
//获取rh对象,并更新“当前线程获取锁的信息”
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;
}
//CAS更新同步状态
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;
}
}
复制代码
/**
HoldConter定义比较简单,就是一个计数器count和线程id两个变量。
*/
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
/**
通过ThreadLocal,HoldCounter就可以与线程绑定了,故而,HoldCounter应该就是绑定线程上的一个计数器,而ThreadLocalHoldCounter则是线程绑定的ThreadLocal。
*/
static final class ThreadLocalHoldCounter extends ThreadLocal {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
复制代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqiLPD9F-1593331558416)(data:image/svg+xml;utf8, )]
public Condition newCondition() {
return sync.newCondition();
}
在Sync中
final ConditionObject newCondition() {
return new ConditionObject();
}
在AbstractQueuedSynchronizer中:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient Node firstWaiter;
private transient Node lastWaiter;
public ConditionObject() { }
2、节点1执行Condition.await()
(1)将head后移。
(2)释放节点1的锁,并从AQS等待队列中移除。
(3)将节点1加入到Condition的等待队列中。
(4)更新lastWaiter为节点1。
3、节点2执行Condition.signal()操作
(5)将firstWaiter后移。
(6)将节点4移除Condition队列。
(7)将节点4加入到AQS的等待队列中去。
(8)更新AQS的等待队列的tail。
/**
* Condition实现简单的生产者消费者
*/
public class ConditionDemo {
private LinkedList<String> buffer; // 容器
private int maxSize; //容量
private Lock lock;
private Condition fullCondition;
private Condition notFullCondition;
ConditionDemo(int maxSize) {
this.maxSize = maxSize;
buffer = new LinkedList<>();
lock = new ReentrantLock();
fullCondition = lock.newCondition();
notFullCondition = lock.newCondition();
}
/**
* 生产者
* @param produceStr
* @throws InterruptedException
*/
public void set(String produceStr) throws InterruptedException {
//获得锁
lock.lock();
try {
while (maxSize == buffer.size()) {
notFullCondition.await();
}
buffer.add(produceStr);
fullCondition.signal();
} finally {
//释放锁
lock.unlock();
}
}
/**
* 消费者
* @return
* @throws InterruptedException
*/
public String get() throws InterruptedException {
String consumeStr;
lock.lock();
try {
while (buffer.size() == 0) {
fullCondition.await();
}
consumeStr = buffer.pollFirst();
notFullCondition.signal();
} finally {
lock.unlock();
}
return consumeStr;
}
}
大家有什么要说的,欢迎在评论区留言
对了,小编为大家准备了一套2020最新的Java资料,需要点击下方链接获取方式
1、点赞+评论(勾选“同时转发”)
学习java,你掌握这些。二三线也能轻松拿8K以上