目前在学习《java并发编程的艺术》这本书,有好多东西都是一知半解,于是决定读一下各个锁实现的源码,加深对锁获取释放以及线程中断的理解。在此做个笔记。
先说线程的中断,贴上部分源码。
//线程的interrupt()方法源码,即new Thread().interrupt()
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
//interrupt0()方法源码
private native void interrupt0();//该方法是一个native方法,暂且将其认为是jvm来实现的
线程被中断后,会设置一个标志位代表线程被中断。
//Thread.interrupted()源码
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
再往下看,我们发现isInterrupted方法也是一个native方法
private native boolean isInterrupted(boolean ClearInterrupted);
由上述源码我们可以看出,加入新建一个线程,然后对其进行中断,则jvm会执行相关操作对该线程设置一个中断标志位,Thread的interrupted()也只是返回该标志位,然后isInterrupted根据传入的boolean变量ClearInterrupted来判断是否在返回当前中断标志之后擦出中断标志。
我之前一直有一个问题:这个线程的中断到底是干什么用的,为什么wait,sleep以及Condition中的await是响应中断的方法,响应中断和不响应中断有什么区别。下面结合源码的走读说一说我自己的理解。
1.回顾一下synchronized
我们都知道synchronized是独占锁,在同步块中可以使用wait以及notify来实现等待和通知的机制。此处解释一个一直以来困扰我自己的问题,一个线程wait后,如果被打断,那打断之后wait到底是如何执行的。下面直接上代码:
//随便写一个用来构造线程的类,用来走读源码
public class Test {
private Map<?,?> map = new HashMap<String,Object>();
public void testsyn() {
synchronized (map) {
try {
wait();// ----1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//wait方法
public final void wait() throws InterruptedException {
wait(0);//-------2
}
//wait(long timeout)
public final native void wait(long timeout) throws InterruptedException;//------3
线程执行wait之后其实是执行第3步的native方法,通过查阅资料得知:若此时线程被中断,则wait方法中会根据线程的中断状态来判断是否抛出异常,如果线程未被中断,则从wait方法返回时一定获取了锁,否则线程一直阻塞在wait方法内(之前此处解释有误,如如果线程被中断,则抛出异常,线程中相应的执行逻辑不会执行,走到catch中然后finally后结束)。线程被中断后返回,此时带回InterruptedException,并在上述代码 1处抛出,再由trycatch检测到,在catch中可以加入一些自定义的逻辑。
2.ReentrantLock源码走读
还是直接上代码,在代码中一步步进行解析:
//-------------1
public static void main(String[] args){
//一般我们再使用的时候都是直接new一个ReentrantLock对象然后调用他的lock方法
ReentrantLock lock = new ReentrantLock();//默认使用非公平的获取方式
lock.lock();
Condition cd = lock.newCondition();
try {
cd.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//-------------2.ReentrantLock()的构造函数:
//默认非公平
public ReentrantLock() {
sync = new NonfairSync();
}
//可选公平
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
先分析非公平锁的获取:
//------------3.NonfairSync是Sync的一个实现类,juc下的锁都是通过在类内部实现AbstractQueuedSynchronizer并自定义实现相应的模板方法,是典型的模板方法模式。
//NonfairSyn中的两个方法,从lock方法开始一步步进入源码。
final void lock() {
if (compareAndSetState(0, 1))//-----首先尝试修改同步状态,如果修改成功,证明获取到锁,由lock方法返回,此时流程结束。
setExclusiveOwnerThread(Thread.currentThread());
else//如果没有获取到,则证明存在竞争,进入acquire方法。
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//--------4 acquire方法是AbstractQueuedSynchronizer提供的方法,它会调用子类的tryAcquire方法,此处即NonfairSync中的protected final boolean tryAcquire(int acquires)
public final void acquire(int arg) {
//tryAcquire方法用来获取锁,若成功则由方法返回,然后acquire方法返回,流程结束。如果不成功,则用当前线程信息构造节点加入同步队列,此处Node.EXCLUSIVE代表独占式获取。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//------5先看tryAcquire方法,即调用的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //-----如果同步状态为0,则证明此时没有其他线程成功获取到锁,然后尝试获取,获取成功返回true。流程结束,一直返回返回到最初使用lock()方法的地方,即获取锁成功。
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果同步状态不为0,证明有线程获取到了同步状态,此时判断是否是当前线程已经获取了同步状态,如果是则增加同步状态,当前线程再次获取锁成功。这也是ReentrantLock支持锁重入的实现。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//有其他线程获取了同步状态,nonfairTryAcquire也是成功返回,返回false。
return false;
}
nonfairTryAcquire方法返回之后,再翻上去看4步骤中的判断条件:!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg),注意tryAcquire前面的非以及判断条件中短路运算的用法。
获取锁失败时,需要构造当前线程的节点并加入同步队列,注意此时还在acquire方法的执行流程中。继续上代码,接上面的代码部分:
//------6构造节点,这部分代码没有走读,参考《并发编程艺术中的解释》
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;
}
//-------7。节点构造成功并加入同步队列后执行acquireQueued方法开始获取同步状态
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;//此时线程是由前驱释放同步状态来唤醒的,所以中断标志为false。acquireQueued方法返回,一直到最上层acquire方法返回,此时成功获取到锁。否则执行下面代码。
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//--------8.shouldParkAfterFailedAcquire//判断当前线程是否找到合适的休息点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)//已经找到,即当前节点可以被前驱唤醒。回到if判断条件,此时当前线程可以进入阻塞状态,即等待前驱的唤醒。
return true;
if (ws > 0) {
//如果前驱节点中的线程已经被取消,则继续查找状态为signal的节点并将此节点设置为当前节点的前驱,设置完之后shouldParkAfterFailedAcquire返回为false。继续7中的for循环来获取锁。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//-------9 parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//park方法用于阻塞当前线程
return Thread.interrupted();
}
//locksupport中的park方法
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
//执行park方法之后,线程阻塞需要等待前驱的唤醒或者中断,个人理解public native void park(boolean paramBoolean, long paramLong);方法在被唤醒或者中断后才从该方法返回,一步一步直接返回到7中继续获取锁。否则,acquire方法一直没有返回,即线程阻塞等待。如果是被中断唤醒则将中断标志位记录并返回。
最后执行selfInterrupt将中断状态补上,以便后续的自定义操作。
关于acquireQueued中的cancelAcquire方法,结合书中:从lock方法返回一定是获取到了锁。个人的理解是,如果cancelAcquire得到执行,则证明当前线程在获取锁的过程中出现了异常或者错误,该错误是关于线程本身的一些错误,导致线程蹦掉了,此时线程中的任务不会执行,即使获取锁失败最终从lock方法返回也不会有问题,因为当前线程已经挂了。
由上述走读可知,lock方法是不支持中断的。下一篇将继续走读Condition中的await方法以及响应中断获取锁的方法。