1、线程状态及切换

  Java中的线程有六种状态,使用线程Thread内的枚举类来实现,如下,我对每个状态都进行了一定的解释。

  public enum State {
/** 表示一个线程还没启用(即未调用start方法)*/
NEW, /**
* JVM中执行的线程都是处于这个状态的,但是处于这个状态不一定在JVM中执行,
* 也就是说,只有这个状态有资格被JVM调度从而获得时间片执行。
*/
RUNNABLE, /**
* 线程在等待获取锁资源从而进入阻塞状态,
* 在这个状态中,其一直监视锁的动态,随时准备抢占锁
* 若获得锁资源,重新进入RUNNABLE状态
*/
BLOCKED, /**
* 当调用Object.wait、Thread.join或者LockSupport类的park方法的时候,线程进入此状态,
* 该状态若无其他线程主动唤醒,则无期限的等待。
* 唤醒的方法包括:Object.notify(唤醒随机一个)、Object.notifyAll(唤醒全部线程),
* 被唤醒的线程重新进入RUNNABLE状态
*/
WAITING, /**
* 同WAITING状态,不过不同的是调用的方法加上了时间的限制,
* 例如:Object.wait(10)、Thread.sleep(10)、Thread.join(10)、LockSupport.parkNanos(10)、LockSupport.parkUntil(10)这些方法
* 唤醒的方法有两种:
* 1、时间过期。
* 2、其他线程调用了notify或者notifyAll
* 唤醒之后同样进入RUNNABLE状态
*/
TIMED_WAITING, /** 线程的终点(正常死亡或者被终止)*/
TERMINATED;
}

  除了NEW和TERMINATED之外,其他的状态都是可以相互转换的,其转换过程如下图所示

  这里特别讲一下RUNNABLE状态,在这个状态中线程并不一定在执行程序,只有被JVM调度的线程才能获得执行的时间片,并且只有这个状态的线程才能够获得时间片,换句话说,被JVM调度并且获得时间片是只属于处于RUNNABLE状态线程的权利。为了便于理解,可以将RUNNABLE分成RunnableRunning两个状态(当然,你也可以换成其他的,这里我只是自己好理解),那么上面的线程转换图就转变成了下面这样(参考《Java并发编程的艺术》中的线程状态图):

  关于线程状态转换的例子,可以通过下面的代码加深理解

public class Test {
public static void main(String[] args) {
Test test = new Test();
// 1.NEW状态
Thread thread = new Thread(() -> {
// 3.进行test对象锁的争夺,若抢到锁则继续执行,否则进入BLOCKED状态监控该锁,重新获得后进入RUNNABLE
synchronized (test) {
try {
// 4.进入TIMED_WAITING状态,100ms后重新进入RUNNABLE状态争夺时间片
Thread.sleep(100);
// 5.重新获得时间片之后,进入WAITING状态
test.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 6.正常run()方法执行完毕后线程结束,进入TERMINATED
});
// 2.调用start()方法,线程进入RUNNABLE状态
thread.start();
}
} 注:代码执行的顺序为注释的序号

2、正确的结束一个线程

  在上面的例子中我们看到线程的run方法正常执行完毕之后线程就正常死亡进入TERMINATED状态了,那么如果我们有中途停止线程的需求,我们应该如何正确的结束一个线程呢?

  1. 使用interrupt()方法:在线程内部,其定义了一个变量来标识当前线程是否处于被打断状态,调用interrupt()方法则使这个状态变为true;换句话说,线程在阻塞状态的时候会检查标识状态,如果是true,则抛出换句话说,线程在阻塞状态的时候会检查标识状态,如果是true,则抛出InterruptedException,否则阻塞,所以不会打断非阻塞的线程,即对正在运行的线程此方法无效。我们采用这个方法加异常处理的方式来结束一个线程。

      public static void main(String[] args) {
    Thread thread = new Thread(() -> {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    // 这里的return是必须的,原因后面说明
    return;
    }
    System.err.println("thread interrupt test...");
    });
    thread.start();
    thread.interrupt();
    System.out.println("main thread end...");
    } // 结果图:异常后面的语句不会打印
      这里关于线程中的打断标识变量(之后以interrupt称)需要说明的是,在特定的情况下其状态会被重置。
       1、线程内部在catch了异常了之后interrupt的状态会被重置为false。
    2、线程调用了Thread.interrupted()方法之后,interrupt的状态会被重置为false。如果需要判断线程是否中断的话可以使用对象方法isInterrupted(),此方法不会重置。
    所以在刚才的代码中需要加入return来结束线程,否则的话线程还是会继续往下执行,如下图
    使用isInterrupted()实现:
    public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
    int k = 0;
    while (k++ < 10) {
    System.out.println("do something..." + k);
    }
    }
    System.err.println("thread end...");
    });
    thread.start();
    Thread.sleep(1);
    // 主线程流程执行完了,需要停止线程
    thread.interrupt();
    }

     

  2. 使用标识位来实现:定义一个变量标识线程是否终止,若终止了则退出run方法。跟上面isInterrupted()的实现一样,不过换成了volatile变量而已。
    public class Test {
    
        public static volatile boolean interrupted = false;
    
        public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
    while (!interrupted) {
    int k = 0;
    while (k++ < 10) {
    if (interrupted) {
    System.err.println("thread invoke end....");
    return;
    }
    System.out.println("do something..." + k);
    }
    }
    System.err.println("thread end...");
    });
    thread.start();
    Thread.sleep(1);
    // 主线程流程执行完了,需要停止线程
    interrupted = true;
    }
    }
    // 结果图

3、stop()方法——不正确的线程中断方法

    在线程提供的方法中还有一个方法可以强制关闭线程——stop()。这个方法可以说是相当的霸道,给人一种“我不管,我就是要你现在立刻死亡(指线程)”的感觉,并且其还会释放线程所有的锁资源,这样可能会导致出现数据不一致从而出现线程不安全的情况,如下面例子。

public class Test {

        public static volatile boolean flag = false;
public int state = 0; public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread thread = new Thread(() -> {
synchronized (test) {
try {
test.state = 1;
Thread.sleep(100);
if (flag) {
test.state = 2;
}
System.err.println("thread execute finished...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(1);
thread.stop();
flag = true;
System.out.println("state状态:" + test.state);
}
}
// 在这段代码中,进入线程时默认将state赋为1,接着过一段时间后如果触发了特定条件则把state赋为2,但是在特定条件触发之前,线程就被终止掉了,这个特定条件虽然符合但却没办法执行,从而导致数据的不一致。
// 结果图
  

  所以,我们应该采用上面两种正确的方式而不是stop()来中止线程。此外,stop()方法若在线程start()之前执行,那么在线程启动的时候就会立即死亡。

若有不对之处,望各位不吝指教(反正免费,对吧)。

Java线程状态和关闭线程的正确姿势的更多相关文章

  1. Java中的线程状态转换和线程控制常用方法

    Java 中的线程状态转换: [注]:不是 start 之后就立刻开始执行, 只是就绪了(CPU 可能正在运行其他的线程). [注]:只有被 CPU 调度之后,线程才开始执行, 当 CPU 分配给你的 ...

  2. java线程状态和获取线程基本信息

    1. 线程状态 新生状态 用 new 关键字建立一个线程后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态. 就绪状态 处于就绪状态线程具备了运行 ...

  3. java通过代码控制线程状态,解决线程不安全的问题。

    写两个类,Input,output 两个都是使用同步代码块的方式实现线程间的同步 input类,是为变量赋值 output类,是打印变量 由于线程争夺cpu造成数据的不匹配 通过,设立一个 flag ...

  4. C#winform程序关闭计算机的正确姿势

    /// <summary> /// 计算机电源控制类 /// </summary> public class EnvironmentCheckClass { [DllImpor ...

  5. [译]线程生命周期-理解Java中的线程状态

    线程生命周期-理解Java中的线程状态 在多线程编程环境下,理解线程生命周期和线程状态非常重要. 在上一篇教程中,我们已经学习了如何创建java线程:实现Runnable接口或者成为Thread的子类 ...

  6. Java内存泄漏分析系列之四:jstack生成的Thread Dump日志线程状态

    原文地址:http://www.javatang.com Thread Dump日志的线程信息 以下面的日志为例: "resin-22129" daemon prio=10 tid ...

  7. JAVA体系的线程的实现,线程的调度,状态的转换

    java体系中线程的实现 1.使用内核线程实现 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上,每个内核 ...

  8. Java Thread系列(二)线程状态

    Java Thread系列(二)线程状态 一.线程的五种状态 新建状态(New):新创建了一个线程对象,尚未启动. 就绪状态(Runnable):也叫可运行状态.线程对象创建后,其他线程调用了该对象的 ...

  9. Java线程状态切换以及核心方法

    1.Java线程状态 1.1 线程主要状态 ①初始(NEW):新创建了一个线程对象,但还没有调用start()方法.②运行(RUNNABLE):Java线程中将就绪(ready)和运行中(runnin ...

随机推荐

  1. Java的异常:Error与Exception

    一. 异常机制的概述      异常机制是指当程序出现错误后,程序如何处理.具体来说,异常机制提供了程序退出的安全通道.当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器. 程序错误分 ...

  2. 小白学习VUE第一篇文章---如何看懂网上搜索到的VUE代码或文章---使用VUE的三种模式:

    小白学习VUE第一篇文章---如何看懂网上搜索到的VUE代码或文章---使用VUE的三种模式: 直接引用VUE; 将vue.js下载到本地后本目录下使用; 安装Node环境下使用; ant-desig ...

  3. unittest核心要素

    1 TestCase 一个TestCase的实例就是一个测试用例.什么是测试用例呢?就是一个完整的测试流程, 包括测试环境的准备(setUp),执行测试代码(run),以及测试后环境的还原(tearD ...

  4. 29道Zookeeper面试题超详细(附答案)

    原文链接 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件 ...

  5. [Leetcode] 第334题 递增的三元子序列

    一.题目描述 给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列. 数学表达式如下: 如果存在这样的 i, j, k,  且满足 0 ≤ i < j < k ≤ n-1, ...

  6. [C++]invalid initialization of non-const reference of type 'std::__cxx11::string& {aka std::__cxx11::basi

    解决方法:在参数前面加一个cosnt或者把引用符号去掉

  7. 【linux】【jenkins】自动化运维七 整合sonarqube代码审查

    1.安装插件:SonarQube Scanner for Jenkins 插件安装教程参考:https://www.cnblogs.com/jxd283465/p/11542680.html 2.So ...

  8. JQuery对于动态生成的标签绑定事件失效

    JQuery对整个html文档进行dom操作后,我们要想动态绑定事件,有两种方法 1.在进行dom操作时,在标签中写上onclick="afun()" 2.利用document的操 ...

  9. Windows server 2008 快速搭建域环境

    之前根据网上的教程搭建,然后出现了很多问题,最后摸索出了一个比较稳妥一点的方法. 对于选系统这里,虽然上一篇文章已经说过了,这里也再强调一下,我使用的是08的系统,使用其他系统的暂不做评价,使用08系 ...

  10. Kerberoasting攻击

    前面我们介绍了<Windows本地认证>.<Windows网络认证>.<Windows域认证>和<SPN扫描>,这次继续讲解域内相关的东西. 0x01介 ...