• 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方法可以读取到线程该标志位,从而判断线程是否被中断,进而执行退出任务的策略判断。

    不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之
  • 相关阅读:
    委托操作控件使用01
    C#读写ini
    List集合的使用小技巧|非常实用首先举例2个集合A,B. List<i
    xiaoqiang
    c#遍历目录及子目录下某类11型的所有的文件
    2015实习生面试记录
    Sliding Window Maximum
    Construct Binary Tree from Preorder and Inorder Traversal
    Binary Search Tree Iterator
    Populating Next Right Pointers in Each Node
  • 原文地址:https://www.cnblogs.com/lauyu/p/5097565.html
Copyright © 2020-2023  润新知