线程生命周期5种状态

介绍

  线程的生命周期经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Bolocked)和死亡(Dead)

状态转换图

新建(New)

  程序使用new关键字创建一个线程之后,该线程就处于新建状态,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值不会执行线程的线程执行体。如Thread thread = new Thread()

就绪(Runnable)

  也称为“可执行状态”,线程对象调用start()方法后,该线程处于就绪状态。如thread.start()。Java虚拟机会为其创建方法调用栈和程序计数器(线程私有),处于就绪状态的线程并没有开始运行,只是表示该线程可以运行,线程何时运行取决于JVM中线程调度器的调度。

运行(Running)

  处于就绪状态的线程获得CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。(注意:线程只能从就绪状态进入到运行状态)

阻塞(Boloked)

  阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。当调用sleep()、一个阻塞式IO方法、同步锁、等待通知、suspend()方法挂起都会使线程进入阻塞状态。

  • 线程调用sleep()方法主动放弃所占用的处理器资源;
  • 线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;
  • 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;
  • 线程在等待(wait())某个通知(notify());
  • 程序调用了线程的suspend()方法将该线程挂起,但这个方法易造成死锁,应该避免使用。

  线程从阻塞状态解除——进入就绪状态的过程:

  • 调用sleep()方法的线程经过了指定时间
  • 线程调用的阻塞式IO方法已经返回
  • 线程成功地获得试图取得的同步监视器(锁)
  • 线程正在等待某个通知时,其他线程发出了一个通知
  • 处于挂起状态的线程被调用了resume()恢复方法。

死亡(Dead)

以如下3种方式结束线程

  • run()call()方法执行完成,线程正常结束;
  • 线程抛出一个未捕获的ExceptionError
  • 直接调用该线程的stop()方法来结束该线程(该方法易造成死锁,不推荐使用)

注意:

  • 当抛出一个异常后程序会结束,所以线程会终止;
  • sleep()方法会阻塞一个线程并不会终止;
  • 创建一个新的线程也不会终止另一个线程。

判断线程是否死亡

  可以通过isAlive()方法,线程对象的isAlive()方法返回true,即为线程存活;返回false,即为线程死亡。

  线程处于就绪、运行、阻塞状态时,isAlive()返回true;线程处于新建、死亡状态时,isAlive()返回false

start()和run()方法详解

start()和run()介绍

  当程序使用new关键字创建了一个线程后,该线程就处于新建状态,此时它和其他Java对象是一样的,只是由JVM为其分配内存,并初始化其成员变量的值(此时线程对象没有任何的行为,也不执行线程执行体)。

  当线程对象调用了start()方法后,线程就处于就绪状态,JVM为其创建方法调用栈和程序计数器,处于这个状态中的线程还没有真正的开始运行,只是表示这个线程此时是一个可运行状态。何时能运行?取决于JVM的线程调度器的调度。

  处于就绪状态的线程获取CPU执行权限,开始执行run()方法的线程执行体,此时线程处于运行状态。(若只有一个CPU,任何时刻只能有一个线程处于运行状态,多线程处理任务时,会给人一种并发错觉,实际是CPU执行速度较快,多线程交织执行任务而已)

start()方法源码

 public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
//若线程不是就绪状态,就抛出异常
if (threadStatus != 0)
throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
//将线程添加到ThreadGroup中
group.add(this); boolean started = false;
try {
//通过start0()方法启动线程
start0();
//设置线程启动的started标志位
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}

  start()实际上通过本地方法start0()启动线程,会新运行一个线程,新线程会调用run()方法。

run()方法源码

    @Override
public void run() {
if (target != null) {
target.run();
}
}

  targetRunnable对象run()直接调用Thread线程Runnable成员run()方法,并不会新建一个线程。

线程控制方法

sleep()方法

sleep()方法介绍

  1. sleep(long millis)方法是Thread类的一个静态方法,作用是让当前线程暂停一段时间,并进入阻塞状态。

sleep()方法重载方式

  • public static native void sleep(long millis) throws InterruptedException:让当前正在执行的线程暂停millis毫秒,并进入阻塞状态。
  • public static void sleep(long millis, int nanos) throws InterruptedException:让当前正在执行的线程暂停millis毫秒+nanos毫微秒,并进入阻塞状态。(很少用)

sleep()示例

通常用法就是

//让当前线程睡眠1000毫秒,即暂定1s
Thread.sleep(1000);

yield()方法

yield()方法介绍

  1. yield()方法让当前正在执行的线程暂停,但不会阻塞线程,只是让线程转入就绪状态。
  2. yield()方法让当前线程暂停,让系统的线程调度重新调度一次,所以会出现当某个线程调用了yield()方法后,线程调度器又重新将它调度出来执行。
  3. yield()方法让当前线程暂停后,只有优先级>=当前线程的处于就绪状态的线程才能获取CPU执行权限。

yield()方法重载

  • public static native void yield();:静态方法。

yield()示例

//让当前线程暂停
Thread.yield();

线程优先级

  1. 每个线程执行都有一定的优先级,优先级高的线程获得CPU执行权限的机会比较大。
  2. 每个线程默认的优先级与创建它的父线程的优先级相同。所以main线程的优先级一般和自己创建的子线程优先级一样。
  3. Thread类提供setPriority(int newPriority)getPriority()方法设置和返回指定线程的优先级。其中setPriority()方法的参数可以是一个整数(1-10之间),也可以是静态常量。

    MAX_PRIORITY:值为10.

    MIN_PRIORITY:值为1.

    NORM_PRIORITY:值为5.

join()方法

join()方法介绍

  1. Thread类提供join()方法让一个线程等待另一个线程完成的方法;就是将指定的线程加入到当前线程,这样两个交替执行的线程就变成了顺序执行的线程,如线程A调用了线程B的join()方法,则线程A会等待线程B执行完毕后才会继续执行自己。
  2. join()方法由使用线程的程序调用,调用线程调用线程t的t.join()方法后将会被阻塞,直到线程t执行完毕,调用线程才能继续执行。一般就是用于主线程内,等待其他线程执行完毕后,再继续执行main()函数。

join()方法重载方式

  • public final void join() throws InterruptedException:等待被join的线程执行完毕。
  • public final synchronized void join(long millis) throws InterruptedException:等待被join的线程的超时时间为millis毫秒。如果在millis毫秒内被join的线程还未结束执行流程,则调用线程不再等待。
  • public final synchronized void join(long millis, int nanos) throws InterruptedException:等待被join的线程的时间最长为millis毫秒+nanos毫微秒。(很少用)

join()方法示例

(1)未使用join()方法

代码

public class JoinMethodTest {

    public static void main(String[] args) {
System.out.println("main thread start"); Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread start");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("child thread finshed");
}
});
thread.start();
System.out.println("main thread finshed");
}
}

运行结果

main thread start
main thread finshed
child thread start
child thread finshed

  可以从运行结果看出,main()主线程日志打印的很快,没有等待子线程打印就结束了。

(2)使用join()方法

代码

public class JoinMethodTest {

    public static void main(String[] args) {
System.out.println("main thread start"); Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread start");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("child thread finshed");
}
});
thread.start();
//加入join()方法等待子线程执行完毕,才执行主线程。
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread finshed");
}
}

运行结果

main thread start
child thread start
child thread finshed
main thread finshed

  从运行结果可以看出,main thread finshed结果是在最后打印的,加入join()方法等待子线程执行完毕,才执行主线程。

6种状态的线程生命周期解释

Q&A

为何启动线程需要用start()方法而不是直接调用run()方法?

  1. 调用start()方法启动线程,系统会将该线程对象的run()方法当作线程执行体来处理。
  2. 直接调用线程对象的run()方法,该方法会被立即执行,而在run()方法返回之前其他线程无法并发执行(系统会将线程对象的当作一个普通对象,将run()方法当作一个普通方法,而不是线程执行体。)

start()方法和run()方法

java Thread中,run方法和start()方法的区别
  • 概念start()是启动线程,让线程从新建状态变为就绪状态;线程得到CPU时间片后,执行run()中的线程执行体;
  • 调用次数start()只能调用一次;run()可以重复调用。
  • 方法类型:启动线程只能用start(),系统会把run()方法当做线程执行体处理;如果直接调用run(),系统会把线程对象当作普通对象,此时run()也是一个普通方法,而不是线程执行体。run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。。
  • 源码start()源码中实际上通过本地方法start0()启动线程,会新运行一个线程,新线程会调用run()方法;而run()源码中targetRunnable对象run()直接调用Thread线程Runnable成员的run()方法,并不会新建一个线程。
  • 多线程:用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。

sleep()和yield()方法的区别

  1. 依赖线程优先级:sleep()方法暂停当前线程后,会给其他线程执行机会,而不在乎其他线程的优先级;

    yield()方法暂停当前线程后,只会给优先级相同或更高的线程执行机会。
  2. 线程转入状态:sleep()方法将线程转入阻塞状态,知道经过阻塞时间才会转入就绪状态;

    yield()方法不会将线程转入阻塞状态,而是将线程转入就绪状态。
  3. 异常声明:sleep()方法声明抛出了InterruptedException异常;

    yield()方法未声明抛出异常。
  4. 可移植性: sleep()方法的移植性比yield()方法好,所以一般使用sleep()方法控制并发编程。

Java—线程的生命周期及线程控制方法详解的更多相关文章

  1. Java学习笔记之——线程的生命周期、线程同步

    一. 线程的生命周期 新建(new Thrad):创建线程后,可以设置各个属性值,即启动前 设置 就绪(Runnable):已经启动,等待CPU调动 运行(Running):正在被CPU调度 阻塞(B ...

  2. java之线程(线程的创建方式、java中的Thread类、线程的同步、线程的生命周期、线程之间的通信)

    CPU:10核 主频100MHz 1核  主频    3GHz 那么哪一个CPU比较好呢? CPU核不是越多越好吗?并不一定.主频用于衡量GPU处理速度的快慢,举个例子10头牛运送货物快还是1架飞机运 ...

  3. Java 之 线程的生命周期(线程状态)

    一.线程的生命周期 (1)新建状态 new 好了一个线程对象,此时和普通的 Java对象并没有区别. (2)就绪 就绪状态的线程是具备被CPU调用的能力和状态,也只有这个状态的线程才能被CPU调用.即 ...

  4. Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

    1.Java使用Thread类代表线程.     所有的线程对象必须是Thread类或其子类的实例. 当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName() ...

  5. java多线程(二)-线程的生命周期及线程间通信

    一.摘要    当我们将线程创建并start时候,它不会一直占据着cpu执行,而是多个线程间会去执行着这个cpu,此时这些线程就会在多个状态之间进行着切换. 在线程的生命周期中,它会有5种状态,分别为 ...

  6. Fragment 的生命周期及使用方法详解

    Fragment 的基础知识介绍 1.1 概述 1.1.1 特性 By hebang32624 Fragment 是 activity 的界面中的一部分或一种行为.可以把多个 Fragment 组合到 ...

  7. 笔记:Maven 生命周期与命令行详解

    Maven 拥有三套相互独立的生命周期,分别是 clean.default和site,clean 生命周期的目的是清理项目,default 生命周期的目的是构建项目,而site生命周期的目的是建立项目 ...

  8. Maven使用教程三:maven的生命周期及插件机制详解

    前言 今天这个算是学习Maven的一个收尾文章,里面内容不局限于标题中提到的,后面还加上了公司实际使用的根据profile配置项目环境以及公司现在用的archetype 模板等例子. 后面还会总结一个 ...

  9. vue2.0项目实战(4)生命周期和钩子函数详解

    最近的项目都使用vue2.0来开发,不得不说,vue真的非常好用,大大减少了项目的开发周期.在踩坑的过程中,因为对vue的生命周期不是特别了解,所以有时候会在几个钩子函数里做一些事情,什么时候做,在哪 ...

随机推荐

  1. JDBC 工具类封装

    每次使用jdbc 我们都要 加载驱动类 创建链接 创建Statement 接口对象执行sql 关闭资源 按照这样的套路可以封装一些重用代码方便在其他方法中调用 package com.xzlf.jdb ...

  2. Java 多线程实现方式二:实现 Runnable 接口

    由于java是单继承,很多时候为了实现多线程 通过继承 Thread 类后,就不能再继承其他类了.为了方便可以通过实现 Runnable 接口来实现,和Tread 类似需要重写run 方法. 下面通过 ...

  3. C# 基础知识系列- 14 IO篇之入门IO

    0. 前言 在之前的章节中,大致介绍了C#中的一些基本概念.这篇我们将介绍一下C#的I/O操作,这将也是一个小连续剧.这是第一集,我们先来简单了解一下C#中的I/O框架. 1. 什么是I/O I/O ...

  4. 用python把技术文档中,每个模块系列截图生成一个动态GIF

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 最近在写技术文档的时候,发现一个问题.对于每个技术步骤,都需要一个截图,这 ...

  5. centos7在命令行下安装图形界面

    yum groupinstall "GNOME Desktop" "Graphical Administration Tools" ln -sf /lib/sy ...

  6. 常用mysql 语句

    ALTER TABLE table_name AUTO_INCREMENT = 1;重置自增字段值从1开始 truncate table `table_name` 清空表,保留数据结构

  7. 2019-2020-1 20199329《Linux内核原理与分析》第十二周作业

    <Linux内核原理与分析>第十二周作业 一.本周内容概述: 通过编程理解 Set-UID 的运行机制与安全问题 完成实验楼上的<SET-UID程序漏洞实验> 二.本周学习内容 ...

  8. 整理高度塌陷与BFC

    当面试官问道你高度塌陷时,人们第一想到的方法一定是 .clearfix::after { content: ''; display: block; clear: both; visibility: h ...

  9. uniapp自定义简单弹窗组件

    2.0(2019-08-31) 船新版本的信息弹窗组件 插件市场地址:http://ext.dcloud.net.cn/plugin?id=672 可以弹出很多条信息,并单独控制消失时间.点击消失. ...

  10. QML文字灰飞烟灭效果

    QML文字灰飞烟灭效果 1,目的 实现文字化作一缕青烟随风而逝的效果. 2,设计分析 在前面的章节中讲述了如何化作光斑碎片逐渐消失的效果,我们可以借鉴它将光斑换成烟雾,再加入端流产生微风浮动,加上字幕 ...