JAVA劝退系列AQS源码阅读之二
前言
继续接上一篇的看AQS源码
lock可重入特性
tryAcquire()锁竞争逻辑
上图代码中,当state不为0时,判断获取锁的线程是否为当前线程
current == getExclusiveOwnerThread()
,是的话信号量加1int nextc = c + acquires;
如果是,则state进行加法,并修改state值,讲一下什么是可重入锁,
public static void reentrantLock(){
String threadName = Thread.currentThread().getName();
lock.lock();
reen2();
lock.unlock();
}
public static void reen2(){
lock.lock();
//业务逻辑
lock.unlock();
}
上图所示的就是可重入特性,再看个例子
public class Juc02_Thread_ReentrantLock {
private static ReentrantLock lock = new ReentrantLock(true);
public static void reentrantLock(){
String threadName = Thread.currentThread().getName();
//默认创建的是独占锁,排它锁;同一时刻读或者写只允许一个线程获取锁
lock.lock();
log.info("Thread:{},第一次加锁",threadName);
lock.lock();
log.info("Thread:{},第二次加锁",threadName);
lock.unlock();
log.info("Thread:{},第一次解锁",threadName);
lock.unlock();
log.info("Thread:{},第二次解锁",threadName);
}
public static void main(String[] args) {
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
reentrantLock();
}
},"t0");
t0.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
reentrantLock();
}
},"t1");
t1.start();
}
}
执行结果
可重入的特性就是加了几次锁,必须释放几次锁。
addWaiter入队介绍
tryAcquire如果为false,就是尝试获取锁失败。就进入到了这个方法acquireQueued
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个性质有俩种,独占(也可叫互斥)和共享,
static final Node SHARED = new Node();//共享
static final Node EXCLUSIVE = null;//独占
除了这俩个还有Node类还有几个重要的变量
volatile Node prev;//前驱指针
volatile Node next;//后继指针
volatile Thread thread; //线程的引用,线程这个对象是需要被保存起来的,以便后续唤醒
volatile int waitStatus;
node除了独占和共享还有,waitStatus节点的生命状态(也叫信号量):
默认0(init),还有
/** 代表出现异常,中断引起的,可被废弃 */
static final int CANCELLED = 1;
/** 可被唤醒 */
static final int SIGNAL = -1;
/** 条件等待 */
static final int CONDITION = -2;
/**
*广播
*/
static final int PROPAGATE = -3;
类似于mysql中某个字段的不同状态,存储不同状态的数据。今天先介绍3种,SIGNAL,CANCELLED
init
再来看这行代码。
Node node = new Node(Thread.currentThread(), mode);
这个结点被创建来之后就是这样,waitStatus为int类型,默认肯定是0了,我们加锁t0加锁,t1排队
当aqs刚new出来的时候,head和tail都是null
那么就会走enq(node)
方法。
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;
}
}
}
}
队列在第一次使用的时候初始化,如下图
t1线程入队,t1前面的 Node t 就是初始化的空结点
最后t.next=node形成双向链表。
acquireQueued
当前节点,线程要开始阻塞
是不是一个线程入队成功,要马上进行休眠?
答案是否定的,
节点阻塞之前还得再尝试一次获取锁:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//是第一个节点,还要再去抢一次,尽可能不让线程阻塞,内核态用户态切换很浪费cpu
//1,能够获取到,节点出队,
if (p == head && tryAcquire(arg)) {//如果前面结点是头部结点,并且能获取锁成功
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 2、不能获取到,阻塞等待被唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
上面代码执行完相当于,
1,能够获取到,节点出队,并且把head往后挪一个节点,新的头结点就是当前节点,对比上面的图
这也就是并发队列的设计
2、不能获取到,阻塞等待被唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//唤醒线程
看下shouldParkAfterFailedAcquire
方法
private static boolean shouldParkAfterFailedAcquire(Node pred,
Node node) {
//pred是前驱节点,node是当前节点
int ws = pred.waitStatus;//前驱节点的状态
if (ws == Node.SIGNAL)//1是可唤醒的
return true;//可以被唤醒的
if (ws > 0) { //代表着节点被废弃掉了
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //为0则cas把前驱节点waitStatus改为1
reAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
通过首节点的状态waitStatus是否为-1,判断当前节点能否被唤醒
总结下:
1第一轮循环,修改head状态,修改signal=-1,标记出可以被唤醒。
2.第2轮循环,执行ws == Node.SIGNAL,return true,阻塞线程,并且需要判断线程是否是有中断信号唤醒的!
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) {
int c = getState() - releases;//1-1为0,锁释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
selfInterrupt()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
当尝试去获取锁失败即tryAcquire为false,以及acquireQueued为true的话,它会执行
selfInterrupt
方法
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
为什么要进行线程的自我中断呢
注意在acquireQueued
的方法体里
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//Thread.interrupted()获取了线程的中断信号,并清掉了线程的中断信号
return Thread.interrupted();
}
所以需要在acquire
方法那里进行一次自我标记,那么为什么这么做呢?
中断是什么?
在很早的时候,直接进行stop0方法,强制把线程杀死。存在各种各样问题。
后来就出现了interrupt方法
lockInterruptibly()
看个例子
@Slf4j
public class Juc05_Thread_Interrupt {
private static ReentrantLock lock = new ReentrantLock(true);
public static void reentrantLock() throws InterruptedException {
String threadName = Thread.currentThread().getName();
boolean flag = false;
lock.lockInterruptibly();
log.info("Thread:{},加锁成功!",threadName);
while(true){
if(Thread.interrupted()){
break;
}
//逻辑,批处理数据
//逻辑
}
lock.unlock();
log.info("Thread:{},锁退出同步块",threadName);
}
public static void main(String[] args) {
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
try {
reentrantLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t0");
t0.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
reentrantLock();
} catch (InterruptedException e) {
e.printStackTrace();
//异常处理
}
}
},"t1");
t1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
}
运行结果:
lockInterruptibly()方法的意思就是,竞争锁失败,如果产生中断会报异常,而lock方法不会报异常。
源码:
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())
//这里和之前不同会抛异常
//跳出for循环执行finally,failed本身为true,cancelAcquire方法把node节点变为
// node.waitStatus = Node.CANCELLED;1为取消状态
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}