Thread.interrupt与LockSupport

首先抛几个问题:

  • Thread.interrupt()方法和InterruptedException异常的关系?是由interrupt触发产生了InterruptedException异常?
  • Thread.interrupt()会中断线程什么状态的工作? RUNNING or BLOCKING?
  • LockSupport.park()和unpark(),与object.wait()和notify()的区别?
  • LockSupport.park(Object blocker)传递的blocker对象做什么用?
  • LockSupport能响应Thread.interrupt()事件不?会抛出InterruptedException异常?

interrupt

​ 每个线程都有一个interrupt status标志位,用于表明当前线程是否处于中断状态。调用interrupt()仅仅是置线程的中断状态位,不会停止线程。 在Core Java中有这样一句话:”没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 “。

Thread的interrupt相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//置线程的中断状态
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();
}
1
2
3
4
//返回线程的中断状态,并清除中断状态
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
1
2
3
4
//线程是否中断
public boolean isInterrupted() {
return isInterrupted(false);
}
1
private native boolean isInterrupted(boolean ClearInterrupted);

注意,public static boolean interrupted()会check当前线程是否处于interrupt,并重置interrupt信息。

public void interrupt()不会中断正在执行的线程,只是将线程的标志位设置成true。但是当线程被Object.wait, Thread.joinThread.sleep等方法阻塞时,调用线程的interrput()方法,可想而知,没有占用CPU运行的线程是不可能给自己的中断状态置位的,因此会抛出InterruptedException,同时会清除线程的中断状态,因此在catch块中捕获到这个异常时,isInterrupted()始终都为false。

为什么有InterruptedException?如果线程被阻塞,它便不能核查共享变量,也就不能停止。例如调用Object.wait()ServerSocket.accept()DatagramSocket.receive()时,都可能永久的阻塞线程。即使发生超时,在超时期满之前持续等待也是不可行和不适当的。Thread.interrupt()方法不会中断一个正在运行的线程,但是该方法可以在线程受到阻塞时抛出一个中断异常(InterruptedException),这样线程就得以提早地终结被阻塞状态。

LockSupport

​ LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport.park()LockSupport.unpark()实现线程的阻塞和唤醒的。

​ LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可。这个“许可”是不能叠加的,“许可”是一次性的。比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。

​ 注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。

​ 实际上,park函数即使没有“许可”,有时也会无理由地返回,这点等下再解析。

​ 在Java5里是用wait/notify/notifyAll来同步的。wait/notify机制有个很蛋疼的地方是,比如线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待。另外,是调用notify,还是notifyAll?notify只会唤醒一个线程,如果错误地有两个线程在同一个对象上wait等待,那么又悲剧了。为了安全起见,貌似只能调用notifyAll了。

​ park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态。

1
2
3
4
public static void main(String[] args) {
LockSupport.park();
System.out.println("block.");
}

运行该代码,可以发现主线程一直处于阻塞状态。因为许可默认是被占用的,调用park()时获取不到许可,所以进入阻塞状态。

如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态,而且LockSupport是不可重入的,如果一次unpark,多次park,当前线程也会阻塞(可以理解对某一线程许可证有且仅有一个)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
Thread thread = Thread.currentThread();
LockSupport.unpark(thread);//释放许可
LockSupport.park();// 获取许可
System.out.println(111);

LockSupport.unpark(thread);//释放许可
LockSupport.unpark(thread);//释放许可
LockSupport.park();// 获取许可
System.out.println(222);

LockSupport.unpark(thread);//释放许可
LockSupport.park();// 获取许可
LockSupport.park();// 获取许可
System.out.println(333);
}

这段代码打印出111和222,不会打印333,因为第二次调用park的时候,线程无法获取许可出现阻塞。

线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Thread t = new Thread(()->{
System.out.println(111);
//等待或许许可
LockSupport.park();
System.out.println("thread over." + Thread.currentThread().isInterrupted());
});
t.start();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程
t.interrupt();

这段代码打印出111和thread over.true。


最后解答之前的问题:

问题1: Thread.interrupt()方法和InterruptedException异常的关系?是由interrupt触发产生了InterruptedException异常?

答: Thread.interrupt()只是在Object.wait() Object.join()Object.sleep()几个方法会主动抛出InterruptedException异常。而在其他的的block常见,只是设置了Thread的一个标志位信息,需要程序自我进行处理。

问题2:Thread.interrupt()会中断线程什么状态的工作? RUNNING or BLOCKING?

答:Thread.interrupt设计的目的主要是用于处理线程处于block状态,比如wait(),sleep()状态就是个例子。但可以在程序设计时为支持task cancel,同样可以支持RUNNING状态。比如Object.join()和一些支持interrupt的一些nio channel设计。

问题3: LockSupport.park()unpark(),与object.wait()notify()的区别?

答:

  1. 面向的主体不一样。LockSuport主要是针对Thread进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程。
  2. 实现机制不同。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉。

问题4: LockSupport.park(Object blocker)传递的blocker对象做什么用?

答: 对应的blcoker会记录在Thread的一个parkBlocker属性中,通过jstack命令可以非常方便的监控具体的阻塞对象.

问题5: LockSupport能响应Thread.interrupt()事件不?会抛出InterruptedException异常?

答:能响应interrupt事件,但不会抛出InterruptedException异常


转载自:
http://agapple.iteye.com/blog/970055
http://www.tuicool.com/articles/MveUNzF