线程中断/阻塞:interrupt/LockSupport
在Core Java中有这样一句话:”没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 “。
简单定义
每个线程都有一个interrupt status标志位,用于表明当前线程是否处于中断状态。
一般调用Thread.interrupt()会有两种处理方式
Thread的interrupt处理的几个方法:
interrupt设置线程状态设置status=1,标示线程停止了;
interrupted()做了getStatus()并且把status=0;获取线程的标示状态,然后把状态重制为0;
isInterrupted()只是做getStatus();Thread.interrupt()并不是真正的停止当前线程,只是标示一下线程的状态为停止。需要在程序中和Thread.interrupted()配合使用,通过程序代码来做相应的停止执行。
代码说明:
class TestRunnable implements Runnable{
public void run(){
while(true)
{
System.out.println( "Thread is running..." );
long time = System.currentTimeMillis();
空循环1s
while((System.currentTimeMillis()-time < 1000)) {
}
}
}
}
public class ThreadInterruptDome {
public static void main(String[] args){
Thread th1=new Thread(new TestRunnable());
th1.start();
th1.interrupt();
}
}
输出结果说明:
Thread is running…
Thread is running…
Thread is running…
Thread is running..
…
不停的往下输出
Thread.interrupt()执行后,如果程序并没有使用Thread.interrupted()做相应处理,Thread.interrupt()其实是没什么卵用的,线程继续执行。
代码片段2
package cn.thread.first.thread;
class TestRunnable implements Runnable{
public void run(){
while(true)
{
System.out.println( "Thread is running..." );
if(Thread.interrupted()){//获取线程停止状态
break;
}
}
System.out.println("线程停止还是可以执行哦。");
}
}
public class ThreadInterruptDome {
public static void main(String[] args){
Thread th1=new Thread(new TestRunnable());
th1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
th1.interrupt();//设置状态为停止状态
}
}
输出结果:
Thread is running…
Thread is running…
Thread is running…
Thread is running…
….
线程停止还是可以执行哦。
解说:代码里面做了Thread.interrupted()相应逻辑的判断了,然后break了,但是并不意味着当前线程就停止了,前面已经介绍过了interrupt只是标示一个状态而已,然后另外一头获取状态,仅此而已,获取状态后线程该咋的还是可以咋的,所以break只是跳出了while循环,并不是真正意义上的线程停止了,所以看到了“线程停止了还是执行了”这句。
解决方案:把break替换为return;或者interruptException抛异常。这样最后一句就不会打印了。
代码段3
package cn.thread.first.thread;
class MyThreadStop extends Thread {
private int temp;
public MyThreadStop(int temp) {
this.temp = temp;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i=" + i);
if (Thread.interrupted()) {
System.out.println("线程已经停止了哦");
break;
}
}
System.out.println(Thread.currentThread().getName() + temp);
}
}
public class ThreadStopDome {
public static void main(String orgs[]) {
MyThreadStop myThread = new MyThreadStop(1);
myThread.start();
myThread.interrupt();
System.out.println("运行完毕");
}
}
输出结果:
运行完毕
i=0
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.thread.first.thread.MyThreadStop.run(ThreadStopDome.java:15)
i=1
i=2
i=3
i=4
解说:1.在wait,notify,sleep的线程中使用interrupt会抛异常sleep interrupted。
2.上面也说明了,myThread.interrupt()把线程状态设置为停止,而调用一次Thread.interrupted()后,该状态被清除了。所以后续1,2,3,4顺利打印出来了,也没有抛异常。因为myThread.interrupt()线程停止的状态已经被清除了。
LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了主要的线程同步原语。
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport实际上是调用了Unsafe类里的函数。归结到Unsafe里,仅仅有两个函数:
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time);
参数:isAbsolute參数是指明时间是绝对的,还是相对的。
unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。
这个有点像信号量,可是这个“许可”是不能叠加的,“许可”是一次性的。
比如:线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,假设线程A再次调用park,则进入等待状态。
注意。unpark函数能够先于park调用。比方线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时。它发现已经有“许可”了。那么它会立即再继续执行。
看一段代码:
class ThreadParkService extends Thread {
private Thread thread;
public ThreadParkService(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " wakup before");
LockSupport.unpark(thread);//解锁后线程2立马就可以执行了。
try {
System.out.println("解锁后休息三秒哦");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " wakup after");
}
}
public class ThreadParkDome {
public static void main(String args[]) {
Thread thread = Thread.currentThread();
ThreadParkService service = new ThreadParkService(thread);
System.out.println(Thread.currentThread().getName()+" start1");
service.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" center");
LockSupport.park(thread);
System.out.println("end");
}
}
打印结果:
main start1
Thread-0 wakup before
解锁后休息三秒哦
main center
end
Thread-0 wakup after
解说:
主线程main先打印了自己的东西,主线程到了park时被阻塞了。
线程1异步执行打印了 wakup before
main center打印在“解锁后休息3s哦”说明线程1中的unpark先与park执行。
线程1执行了unpark了,这个时候主线程中的park立马被唤醒,开始执行了end。
然后线程1等待了3s后执行了wakup after。
unpark,可以在park之前执行。它们一对一哦。不管什么时候park只要有unpark过一次就好了。
park被阻塞后,unpark执行后park立即被唤醒。与wait与notify不同,notify唤醒wait后,还必须等待notify代码块中的代码执行完毕后wait才能执行。
代码段2:
class ThreadParkService extends Thread {
private Thread thread;
public ThreadParkService(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " wakup before");
LockSupport.unpark(thread);//解锁后线程2立马就可以执行了。
try {
System.out.println("解锁后休息三秒哦");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " wakup after");
}
}
class ThreadParkService2 extends Thread {
private Thread thread;
public ThreadParkService2(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-线程2开始park before");
LockSupport.park(this);//阻塞当前执行的线程,不能阻塞其他线程,看源码哦。里面阻塞时并不需要任何传参。
//LockSupport.park(thread);//这里并不会阻塞thread线程,阻塞的还是当前线程。thread只是做一个标记方便被锁的对象。
System.out.println(Thread.currentThread().getName() + "-线程2park after");
}
}
public class ThreadParkDome {
public static void main(String args[]) {
Thread thread = Thread.currentThread();
ThreadParkService2 service2 = new ThreadParkService2(thread);
System.out.println(Thread.currentThread().getName() + " start2");
service2.start();
System.out.println(" 漂亮的分割线---");
ThreadParkService service = new ThreadParkService(service2);
System.out.println(Thread.currentThread().getName() + " start1");
service.start();
}
}
执行结果:
main start2
漂亮的分割线—
Thread-0-线程2开始park before
main start1
Thread-1 wakup before
解锁后休息三秒哦
Thread-0-线程2park after
Thread-1 wakup after
解说:其实看结果就是线程2阻塞了,线程1唤醒了线程2.就这么简单。
重点来了:
1.park()与park(Object obj)有什么区别?park()是可以不需要参数的哦,就是说加锁时,不需要指定对象。那么它是怎么加锁的呢?点进去看源码
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);//记录当前线程等待的对象(阻塞对象);
UNSAFE.park(false, 0L);//阻塞当前线程;
setBlocker(t, null);//当前线程等待对象置为null。
}
可以看出来,阻塞的时候UNSAFE.park(false, 0L);根本不需要外面的入参数。
那么setBlocker是用来干吗的呢?它是用来做监控用的,当前被阻塞的对象是那个,可以通过LockSupport中的get之类的方法来获取当前被阻塞的对象。
UNSAFE.park(false, 0L);锁住的是当前正在执行的线程哦。
2.代码中线程2有一个参数,我把主线程传递过去了, 想用线程2来阻塞主线程。
main中:
Thread thread = Thread.currentThread();
ThreadParkService2 service2 = new ThreadParkService2(thread);
…
线程2中:
LockSupport.park(thread);
这样是不行的,前面说过park阻塞的是当前正在执行的线程,和外面传递进来的值无关。所以这里被阻塞的还是线程2.
3.unpark需要带上参数,这样才知道解锁那个线程。
LockSupport.park 锁住的是Thread.currentThread()当前线程。
类继承了Thread类后,可以使用this.getName();获取当前线程的名称,但是Thread.currentThread().getName()也获取线程名称。那么它们有什么区别呢?
继承了Thread类后使用this.getName()获取的当前对象的线程名称,而Thread.currentThread().getName()获取的是当前正在运行的线程的名称。
上面代码片段2可以自己执行后测试一下。
synchronized,wait,notify是通过每个对象中的monitor监控器来实现的。
在HotSpot虚拟机的世界里面,每个java线程都有一个Parker的实例,而LockSupper就是通过每个线程里面的Parker实例来实现的。lockSupport底层调用的是UNSAFE.park(),UNSAFE是直接操作操作系统底层的东西,所以没啥好说的。查看这边文章看看了解了解就行。http://www.jianshu.com/p/ceb8870ef2c5
linux与windows中调用cpu的指令有所不同,但都是通过cpu的mutex指令来线程互斥的。
synchronized与lockSupport最终都是一把锁。synchronized锁住的是一件房子,也可以锁住一间房间。而lockSupport锁住的是一个柜子,或者一个抽屉柜。它们所的粒度不同而已,但终究是通过操作系统来实现的。
http://www.cnblogs.com/lcchuguo/p/4855664.html
http://www.cnblogs.com/skywang12345/p/3505784.html(深度好文)
http://www.jianshu.com/p/ceb8870ef2c5