Java多线程学习之线程的状态及中断线程

线程的状态

  1. 新建(new):当线程被创建时,它只会短时间处于这种状态。它已经分配了必要的系统资源,完成了初始化。之后线程调度器将把这个线程转变为可运行或者阻塞状态;
  2. 就绪(Runnable):在这种状态下,只要调度器分配时间片给线程,线程就可以运行了;
  3. 阻塞(Blocked):有某个条件阻止线程运行,调度器将忽略阻塞状态的线程,不会分配时间片给它,直到线程进入就绪状态,它才有可能执行;
  4. 死亡(Dead):处于死亡或者终结状态的线程将不再是可调度的,并且也不会被分配到时间片。任务死亡的方式通常是从run方法返回,或者被中断;

  下面列举线程进入阻塞状态的几个可能原因:

  1. 通过调用sleep(mils)使任务进入指定时间的睡眠状态;
  2. 调用wait使线程挂起,直到线程得到notify或者notifyAll信息,线程才会进入就绪状态;
  3. 任务在等待输入输出完成;
  4. 任务试图调用某个类/对象的同步方法,但是对象锁不可用;

  由于处于阻塞状态的线程被挂起,得不到执行,即是代码中有判断状态值某一点而退出,亦不会到达该点,这种情况下应该强制任务跳出阻塞状态。

中断

  Thread类包含interrupt方法,用于终止被阻塞的任务。调用interrupt方法将设置线程的中断状态,如果一个线程已经被阻塞或者试图执行一个阻塞操作,那么设置线程的中断状态将抛出InterruptedException异常。当抛出InterruptedException异常或者该任务调用Thread.interrupted()时,线程中断状态将被重置。这个中断被阻塞的线程需要我们持有线程对象。

  Java SE5起建议使用Executor来创建并发任务,当在Executor上调用shutdownNow(),那么它将发生一个interrupt给它启动的所有线程。如果只是想关闭某个特定而不是全部的任务,要通过submit来提交任务,返回Future<?>对象,在Future对象上调用cancel来中断线程,也可以传递true给cancel,那么Future对象就会拥有在该线程上调用interrupt以终止线程的权限。

  下面看看不同阻塞条件下线程中断情况:

//定义sleep进入阻塞的任务
public
class SleepBlocked implements Runnable{ public void run() { try { TimeUnit.SECONDS.sleep(10); }catch (InterruptedException e) { System.out.println("interrupt from sleep..."); } System.out.println("exiting from SleepBlocked run"); } } //定义因为IO进入阻塞的任务 public class IOBlocked implements Runnable { InputStream input; public IOBlocked(InputStream input) { this.input = input; } public void run() { try { System.out.println("waiting for input..."); input.read(); }catch (IOException e) { System.out.println("IOException..."); if(Thread.interrupted()) { System.out.println("interrupt from IOBlocked..."); }else { throw new RuntimeException(); } } System.out.println("exiting from IOBlocked......"); } } //定义因为尝试获取对象锁进入阻塞的任务 public class SynchronizedBlocked implements Runnable{ public synchronized void f() { while (true) Thread.yield(); } public SynchronizedBlocked() { new Thread() { public void run() { f(); } }.start(); } public void run() { System.out.println("trying to call f()..."); f(); System.out.println("exiting from SynchronizedBlocked...."); } }

  测试类如下:

public class InterruptedTest {
    private static ExecutorService executor = Executors.newCachedThreadPool();

    static void test(Runnable task) throws InterruptedException{
        //使用Executor的submit方法提交任务,或者线程的参考,以便中断线程
     Future future
= executor.submit(task); TimeUnit.SECONDS.sleep(1); System.out.println("interruptting "+task.getClass().getName());
     //通过传递true给Future对象的cancel方法,运行中断被阻塞的线程 future.cancel(
true); System.out.println("interrupt send to "+task.getClass().getName()); } public static void main(String[] args) throws Exception{ test(new SleepBlocked()); test(new IOBlocked(System.in)); test(new SynchronizedBlocked()); } }
输出:

interruptting com.thread.test.SleepBlocked
interrupt send to com.thread.test.SleepBlocked
interrupt from sleep...  //因为睡眠而阻塞的线程被中断
exiting from SleepBlocked run

//因为IO而阻塞的线程没有被中断
waiting for input...
interruptting com.thread.test.IOBlocked
interrupt send to com.thread.test.IOBlocked

//因为获取对象锁而阻塞的线程没有被中断

trying to call f()...
interruptting com.thread.test.SynchronizedBlocked
interrupt send to com.thread.test.SynchronizedBlocked
exiting from IOBlocked......

  从输出可以看出,可以中断对sleep的调用,但是不能中断试图获取对象锁(synchronized实现)或者执行IO任务的线程。

实现尝试获取对象锁的阻塞中断

  从上面可以知道,以synchronized关键字实现的同步对象锁在阻塞时不能被中断。这里介绍一种既可以提供锁功能,又能够在阻塞时被中断的实现,那就是使用ReentrantLock。

 

public class BlockedMutex{

    private Lock lock = new ReentrantLock();

    public void f() {
        try {
       //一直占有锁,除非被中断 lock.lockInterruptibly(); }
catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+" interrupted from lock acquisition in f()"); } } } public class LockBlocked implements Runnable { private BlockedMutex blockedMutex; public LockBlocked(BlockedMutex blockedMutex) { this.blockedMutex = blockedMutex; } public void run() { System.out.println(Thread.currentThread().getName()+" waiting for blockedMutex...."); blockedMutex.f(); System.out.println(Thread.currentThread().getName()+" Broken out of blocked call"); } } public class InterruptedTest {public static void main(String[] args) throws Exception{ BlockedMutex blockedMutex = new BlockedMutex(); Thread t1 = new Thread(new LockBlocked(blockedMutex)); Thread t2 = new Thread(new LockBlocked(blockedMutex)); t1.start(); t2.start(); t2.interrupt(); } }
输出:

Thread-0 waiting for blockedMutex....
Thread-1 waiting for blockedMutex....
Thread-0 Broken out of blocked call
Thread-1 interrupted from lock acquisition in f()
Thread-1 Broken out of blocked call

 

  两个任务都是使用同一个BlockedMutex实例的f方法,f方法里面是以永久占有锁的方式获取对象锁,这样只有最先被执行的任务能够占有锁,之后的任务将一直等待。测试中见到t1占有锁,t2在等待锁,中断t2,看到t2驱动的任务里面抛出InterruptedException,这也说明ReentrantLock锁确实能够在阻塞下被中断

 

实现IO操作的阻塞中断

 

检查中断

  从上面的学习得知,在线程进入阻塞或者要进入阻塞时,调用线程的interrupt,线程将会抛出异常(IO阻塞或者synchronized阻塞例外,不能被中断)。在任务里面,并不都是可能导致线程进入阻塞的代码,在任务执行不会导致阻塞的代码时,可以通过调用Thread的interrupted方法来检查中断状态,因为Thread的interrupt方法会设置线程的中断标志位,而interrupted方法则能够读取到该标志位,并且重置标志位。请看下面例子:

public class InterruptedCheck implements Runnable {
    private double d = 1d;
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println("sleeping...");
          //线程睡眠2s,进入阻塞,如果在睡眠时间interrupt,将会抛出异常 Thread.sleep(
2000); System.out.println("calculating....");
          //这里以运算模拟任务,要有一定的时间,但是线程不会进入阻塞状态。如果在运算期间进行interrupt,将不会抛出异常,且中断标志位被设置
for(int i=1;i<25000000;i++) { d = d + (Math.PI + Math.E) /d; } } System.out.println("detected interrupted, not from blocked..."); }catch (InterruptedException e) { System.out.println("interrupted from blocked..."); } } } public static void main(String[] args) throws Exception{ Thread thread = new Thread(new InterruptedCheck()); thread.start();
    //这里man的睡眠时间为不同的值,就可以在thread处于不同的状态(sleep进入阻塞或者正常执行运算)interrupt TimeUnit.MILLISECONDS.sleep(
2100); System.out.println("interruptting..."); thread.interrupt(); } 输出:main的sleep时间为1500milsec时 sleeping... interruptting... interrupted from blocked...
main的sleep时间为2100milsec时
sleeping... 
calculating....
interruptting...
detected interrupted, not from blocked...

  可以看到,在可以被中断的阻塞状态下中断线程,将会以抛出异常的形式退出任务。在运行状态下中断线程,被中断的线程的中断标志位被设置,通过interrupted方法可以读取到线程该标志位,从而判断线程是否被中断,进而执行退出任务的策略判断。

posted @ 2016-01-06 01:04  麦香小瑜儿  阅读(2789)  评论(0编辑  收藏  举报