Java线程状态及切换、关闭线程的正确姿势分享

前言

在讲线程之前有必要讨论一下进程的定义:进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。进程实体由程序段, 数据段 PCB(进程控制块)组成。线程又是什么?线程可以看做轻量级进程,线程是进程的执行单元,是进程调度的基本单位

本文将详细介绍关于Java线程状态及切换、关闭线程的相关内容,下面话不多说了,来一起看看详细的介绍吧

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分成Runnable和Running两个状态(当然,你也可以换成其他的,这里我只是自己好理解),那么上面的线程转换图就转变成了下面这样(参考《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状态了,那么如果我们有中途停止线程的需求,我们应该如何正确的结束一个线程呢?

使用interrupt()方法:在线程内部,其定义了一个变量来标识当前线程是否处于被打断状态,调用interrupt()方法则使这个状态变为true。我们采用这个方法加异常处理的方式来结束一个线程。

  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();
}

使用标识位来实现:定义一个变量标识线程是否终止,若终止了则退出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;
 }
}
// 结果图

 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()之前执行,那么在线程启动的时候就会立即死亡。

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Java线程的生命周期和状态控制_动力节点Java学院整理

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable). 注意:不能对已经启动的线程再次调用start()方法,否则会出现Javalang.IllegalThreadStateException异常. 2.就绪状态 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称

  • 学习Java多线程之线程定义、状态和属性

    一 .线程和进程 1. 什么是线程和进程的区别: 线程是指程序在执行过程中,能够执行程序代码的一个执行单元.在java语言中,线程有四种状态:运行 .就绪.挂起和结束. 进程是指一段正在执行的程序.而线程有事也被成为轻量级的进程,他得程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内功空间(代码段.数据段和堆空间)及一些进程级的资源(例如打开的文件),但是各个线程都拥有自己的棧空间. 2. 为何要使用多进程 在操作系统级别上来看主要有以下几个方面: - 使用多线程可以减少程序

  • java中线程的状态学习笔记

    java开发中,我们经常会遇到线程的问题,比如你做一个商城,就需要考虑它的并发问题等等,今天给大家分享一下java中线程的状态 先说线程的第一个状态,是新建状态,这个是线程刚刚创建的时候,如: new Thread(),具体如图 线程的第二种状态是可执行状态,就是调用了start方法后的状态,当然了,一个运行的状态,他有可能是正在运行的,也有可能是没有运行的,只是他的状态是可运行的状态,具体如图 第三种状态是被阻塞或者处于等待的线程,处于这种状态下的线程是不活动且不运行的,比如说调用了wait方

  • Java线程池运行状态监控实现解析

    在实际开发过程中,在线程池使用过程中可能会遇到各方面的故障,如线程池阻塞,无法提交新任务等. 如果你想监控某一个线程池的执行状态,线程池执行类 ThreadPoolExecutor 也给出了相关的 API, 能实时获取线程池的当前活动线程数.正在排队中的线程数.已经执行完成的线程数.总线程数等. 总线程数 = 排队线程数 + 活动线程数 + 执行完成的线程数. 线程池使用示例: private static ExecutorService es = new ThreadPoolExecutor(

  • Java 实现多线程切换等待唤醒交替打印奇偶数

    引言 在日常工作生活中,可能会有用时几个人或是很多人干同一件事,在java编程中,同样也会出现类似的情况,多个线程干同样一个活儿,比如火车站买票系统不能多个人买一到的是同一张票,当某个窗口(线程)在卖某一张票的时候,别的窗口(线程)不允许再卖此张票了,在此过程中涉及到一个锁和资源等待的问题,如何合理正确的让线程与线程在干同一件事的过程中,不会抢资源以及一个一直等待一个一直干活的状况,接下来就聊一下多线程的等待唤醒以及切换的过程,在此就以A和B两个线程交替打印奇偶数的例子为例,代码如下: pack

  • Java线程的新建和就绪状态实例分析

    本文实例讲述了Java线程的新建和就绪状态.分享给大家供大家参考,具体如下: 一 点睛 当线程被创建启动以后,它既不是一启动就进入执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建.就绪.运行.阻塞和死亡5种状态.当线程启动后,它不可能一直霸占CPU独自运行,所以CPU需要在多个线程之间切换,于是线程状态也会多次在运行和就绪之间切换. 当程序使用new关键字创建一个线程后,该线程就处于新建状态. 当线程对象调用start()方法之后,该线程就处于就绪状态,处于这个状态的线程并没有

  • Java实现监控多个线程状态的简单实例

    实例如下: import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * 测试监控类 * * @author * */ public class WatchThread { /** * 测试函数 * * @throws InterruptedException */ public void testThre

  • RxJava的消息发送和线程切换实现原理

    RxJava是一个在Java虚拟机上的响应式扩展,通过使用可观察的序列将异步和基于事件的程序组合起来的一个库. 它扩展了观察者模式来支持数据/事件序列,并且添加了操作符,这些操作符允许你声明性地组合序列,同时抽象出要关注的问题:比如低级线程.同步.线程安全和并发数据结构等. RxJava相信大家都非常了解吧,今天分享一下RxJava的消息发送和线程源码的分析.最后并分享一个相关demo,让大家更加熟悉我们天天都在用的框架. 消息订阅发送 首先让我们看看消息订阅发送最基本的代码组成: Observ

  • 总结Java中线程的状态及多线程的实现方式

    线程的状态 线程状态图: 说明: 线程共包括以下5种状态. 1. 新建状态(New) : 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread(). 2. 就绪状态(Runnable): 也被称为"可执行状态".线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程.例如,thread.start().处于就绪状态的线程,随时可能被CPU调度执行. 3. 运行状态(Running) : 线程获取CPU权限进行执行.需要注意

  • 基于java 线程的几种状态(详解)

    线程可以有六种状态: 1.New(新创建) 2.Runnable(可运行)(运行) 3.Blocked(被阻塞) 4.Waiting(等待) 5.Timed waiting(计时等待) 6.Terminated(被终止) 新创建线程: 当用new操作符创建一个新线程时,如new Thread(r),该线程还没有开始运行,它的当前状态为new,在线程运行之前还有一些基础工作要做. 可运行线程: 一旦线程调用start方法,线程处于runnable状态.在这个状态下的线程可能正在运行也可能没有运行(

随机推荐