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

时间: 2024-10-13 03:16:07

Java多线程学习之线程的状态及中断线程的相关文章

java多线程学习(3)

1)竞争条件 在实际的多线程应用中,通常会有两个或多个线程需要对共同的对象进行共享访问,如果两个线程访问相同的对象,而且每一个都调用了一个会改变对象状态的方法, 那么,线程就会相互倾轧.根据各个线程访问数据的不同顺序,可能会产生腐蚀现象.这种情况通常称为竞争条件. 2)同步 为了多个线程对共享数据的腐蚀,就需要对数据的存取实现同步:常用的同步方法有3种: 1.Reenlock 用Reenlock保护代码块的基本机构如下: 1 Lock myLock=new ReenLock; 2 3 myLoc

[转]Java多线程学习(总结很详细!!!)

Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的线程函数用法.概述等.首先让我们来了解下在操作系统中进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据空间,

java多线程学习(2)

1)Callable和Future Runnable封装一个异步运行的任务:可以当成一个没有任何参数和返回值的异步方法,Callable和 Runnable类似,但是它有返回值和参数. Callable接口是一个参数化的类型,只有一个方法call. 1 public interface Callable<V> 2 3 { 4 5 V call()throws Exception; 6 7 } 类型参数v是指返回值的类型,例如Callable<Integer>代表最终返回一个Inte

java多线程学习(1)

1)多线程与多进程的区别 多线程和多进程有什么区别呢?本质的区别在于每个进程有它自己的变量的完备集,线程则共享相同的数据. 对程序来说,共享的变量会使得线程之间的通信比进程间的通信更加有效和简单:同时,线程相对于进程来说,更加的“轻量级”, 线程的创建和销毁要比 进程的 开销要小的多. 2)多线程程序的构造 多线程的构造通常有两种方法, 第一种方法是,构建一个Thread的子类,并重写它的run()方法: 1 class MyThread extends Thread 2 { 3 4 publi

java多线程学习--java.util.concurrent

CountDownLatch,api 文档:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. 假设我们要打印1-100,最

java 多线程学习(一)

1 public class ThreadA extends Thread { 2 private static int threadID = 0; 3 4 public ThreadA() { 5 super("ThreadID:" + (++threadID)); 6 } 7 8 public void run() { 9 try { 10 System.out.println(getName() + " 线程运行开始!"); 11 for (int i = 0

黑马程序员之Java多线程学习

android培训  java培训 期待与您交流! 这一篇文章主要关于java多线程,主要还是以例子来驱动的.因为讲解多线程的书籍和文章已经很多了,所以我也不好意思多说,呵呵.大家可以去参考一些那些书籍.我这个文章主要关于实际的一些问题.同时也算是我以后复习的资料吧,.呵呵大家多多指教. 同时希望多结交一些技术上的朋友.谢谢. -------------------------------------------------------------------------------------

Java多线程学习幸运飞艇采集器修复

package javastudy01; class MyThread extends Thread {//重写Run方法public void run(){//1.获取当前线程的名字System.out.println(this.getName()+"我是一个线程."); } public static void main(String[] args) {Java多线程学习幸运飞艇采集器修复,需要请搜索[大神源码论坛]dsluntan.com 客服企娥3393756370 V信170

“全栈2019”Java多线程第四章:设置和获取线程名称

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第四章:设置和获取线程名称 下一章 "全栈2019"Java多线程第五章:线程睡眠sleep()方法详解 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Jav