为什么Synchronized不可中断?首先中断操作是Thread
类调用interrupt
方法实现的。基本上所有人都说Synchronized
后线程不可中断,百度后的大部分文章都是这样解释说道:
不可中断的意思是等待获取锁的时候不可中断,拿到锁之后可中断,没获取到锁的情况下,中断操作一直不会生效。
验证真伪
以下为测试理论是否成立的Demo代码示例:
代码语言:javascript复制public class Uninterruptible {
private static final Object o1 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println("t1 enter");
synchronized (o1) {
try {
System.out.println("start lock t1");
Thread.sleep(20000);
System.out.println("end lock t1");
} catch (InterruptedException e) {
System.out.println("t1 interruptedException");
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
System.out.println("t2 enter");
synchronized (o1) {
try {
System.out.println("start lock t2");
Thread.sleep(1000);
System.out.println("end lock t2");
} catch (InterruptedException e) {
System.out.println("t2 interruptedException");
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
// 主线程休眠一下,让t1,t2线程百分百已经启动,避免线程交替导致测试结果混淆
Thread.sleep(1000);
// 中断t2线程的执行
thread2.interrupt();
System.out.println("t2 interrupt...");
}
}
复制代码
运行结果:
代码语言:javascript复制t1 enter
start lock t1
t2 enter
t2 interrupt... // 此处等待了好久好久,一直卡住
end lock t1
start lock t2 // 直到t1执行完释放锁后,t2拿到锁准备执行时,interruptedException异常抛出
t2 interruptedException
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at concurrent.Uninterruptible.lambda$main$1(Uninterruptible.java:48)
at concurrent.Uninterruptible$$Lambda$2/1134517053.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
结果正好印证了Synchronized不可中断的说法:只有获取到锁之后才能中断,等待锁时不可中断。
深入分析Synchronized
为什么Synchronized
要设计成这样,ReentrantLock
都允许马上中断呀,是Synchronized
设计者有意为之还是另有苦衷?
感觉如果设计成这样有点蠢吧,为什么要拿到锁才去中断,毫无理由啊。肯定有阴谋!
后来看了Thread.interrupt()
源码发现,这里面的操作只是做了修改一个中断状态值为true,并没有显式声明抛出InterruptedException
异常。
/**
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and
* it will receive an {@link InterruptedException}.
* 翻译:如果此线程被以下命令(wait、join、sleep)阻塞,他的中断状态会被
* 清除并且会抛出InterruptedException异常
*
* <p> If none of the previous conditions hold then this thread's
* interrupt status will be set. </p>
* 翻译:如果前面的条件都不满足那么将设置它的中断状态
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess(); // 检查权限
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 它是一个native方法, Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
复制代码
得到一个解释说,中断操作只是给线程的一个建议,最终怎么执行看线程本身的状态,那么什么状态做什么事情呢?
- 若线程被中断前,如果该线程处于非阻塞状态(未调用过
wait
,sleep
,join
方法),那么该线程的中断状态将被设为true, 除此之外,不会发生任何事。 - 若线程被中断前,该线程处于阻塞状态(调用了
wait
,sleep
,join
方法),那么该线程将会立即从阻塞状态中退出,并抛出一个InterruptedException
异常,同时,该线程的中断状态被设为false, 除此之外,不会发生任何事。
查看wait
, sleep
, join
方法源码,验证上面的第2点:
/** @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
/** @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final native void wait(long timeout) throws InterruptedException;
/** @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
复制代码
通过注释可以看到这三个方法都会去检查中断状态,随时抛出中断异常。native method属于本地方法了,如果想看内部的实现细节,请各位同为结合hotspot
源码对比阅读,这里就不细说了。
所以说,Synchronized
锁此时为轻量级锁或重量级锁,此时等待线程是在自旋运行或者已经是重量级锁导致的阻塞状态了(非调用了wait
,sleep
,join
等方法的阻塞),只把中断状态设为true,没有抛出异常真正中断。
对比ReentrantLock
那为什么ReentrantLock
可中断呢(未获取到锁也可中断),但是必须使用ReentrantLock.lockInterruptibly()
来获取锁,使用ReentrantLock.lock()
方法不可中断。
来看看ReentrantLock.lockInterruptibly()
源码:
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1); // 调用可中断的获取锁方法
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) // 获取锁时检查中断状态
// 显式抛中断异常
throw new InterruptedException();
if (!tryAcquire(arg)) // 获取不到锁,执行doAcquireInterruptibly
doAcquireInterruptibly(arg);
}
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; // 应用置null,便于GC
failed = false;
// 结束自旋
return;
}
// 检查是否阻塞线程 && 检查中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 显式抛中断异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
从源码可以知道,ReentrantLock.lockInterruptibly()
首次尝试获取锁之前就会判断是否应该中断,如果没有获取到锁,在自旋等待的时候也会继续判断中断状态。这时lockInterruptibly
底层再显式抛错,而不是像Synchronized
那样交由线程自己决定是否抛错。当然lockInterruptibly
获取到锁之后,也是得交由线程自己决定。