本章内容为AQS对线程中断做的处理进行讲解.再之前文章中有讲述AQS的队列原理使用的是park和unpark,既然如此那么思考下几个问题?
- 队列中或者获取锁时是否支持中断?
- 如果是那么他的中断做了什么及如何设计的?
//针对第一个问题,AQS是支持中断的他提供了四个方法,可以查看分为两类
//1.不抛出中断异常 lock/tryLock
//2.抛出中断异常 lockInterruptibly/tryLock(long time, TimeUnit unit)
void lock();
boolean tryLock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//如果但看这四个方法会以为第二种支持,第一种不支持,真的是这样吗?下面详细讲解其原理.
//jvm中断实现
void os::interrupt(Thread* thread) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
if (!osthread->interrupted()) {
osthread->set_interrupted(true);
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
OrderAccess::fence();
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
//如果产生中断则会调用线程的unpark方法,而此时线程正处于park挂起状态,其原理查看前面两章
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
//我们发现发生中断会使得接触park挂起继续执行
//而park中断后紧接着返回当前的中断事件并且清除中断状态
//清除并且记录了当前的状态方便后续使用,下面查看此方法的调用出
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
//此次方法为lockInterruptibly方法内部使用,可以查看当park被唤醒后如果是中断状态则直接抛出异常
private void doAcquireInterruptibly(int arg){
....
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
...
}
//此为tryLock(long time, TimeUnit unit)方法的调用,虽然此处没有使用parkAndCheckInterrupt 但是其原理相同
//并且判断是否为中断,如果存在中断则抛出中断异常
private boolean doAcquireNanos(int arg, long nanosTimeout)
...
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
...
}
//到此已经知道了申明中断的地方,下面为未申明的第一类获取锁的方法
//lock方法的调用,可以看出此处将之前抛出异常改为了中断状态interrupted,而此方法的返回值便是中断状态
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())
//需要提醒的是parkAndCheckInterrupt 清除了中断,并且此处可以看出中断并不影响获取锁
//因为此处理在for(;;)中,将会继续尝试获取锁,直到获取锁成功然后返回interrupted
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//tryLock,只是一个非公平锁获取并不涉及到中断状态,其结果为返回是否获取锁成功
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
到此处两类的四种方法实现都已经讲解完成,下面做总结.
- AQS支持中断,并且做了两种处理.
- 抛出中断异常
- 记录中断状态并且返回,在记录的同时清除线程的中断状态
- 不抛出异常的中断处理,也分为两种
- tryLock 直接获取锁,打破公平锁(此处需要注意)
- lock 获取锁直到成功.忽略所有中断,并且记录中断状态,最终返回到acquire方法,此方法如果发现当前线程被中断,则处理调用线程的interrupt方法,将当前线程再次设置上中断状态(还记得之前提的记录会清除,而此处再次还原),说明lock的中断需要我们进行处理,否则将会在后续的一些操作中如sleep等可中断方法中抛出异常
//此为sleep的实现,如果采用lock 被中断后执行到sleep方法此处将会判断中断状态,如果为true则抛出异常
//并且清除中断状态
if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
网友评论