Java并发编程之线程生命周期、守护线程、优先级、关闭和join、sleep、yield、interrupt
Java并发编程中,其中一个难点是对线程生命周期的理解,和多种线程控制方法、线程沟通方法的灵活运用。这些方法和概念之间彼此联系紧密,共同构成了Java并发编程基石之一。
Java线程的生命周期
Java线程类定义了New、Runnable、Running Man、Blocked和Dead五种状态。

New
当初始化了一个线程对象之后,线程就进入了New的状态。此时JVM会为其分配堆内存、初始化成员变量的值,获取当前线程为父线程。
Runnable
当调用线程对象的start方法之后,就进入Runnable状态。JVM会为其创建虚拟机栈和程序计数器。此时仅表明线程可以开始运行,但何时运行取决于JVM的线程调度器。
Running
当线程获取到CPU资源的时候,就进入Running状态执行线程方法了。如果线程数大于多处理器的数目,会存在多个线程轮换,尽管多个处理器会同时并行处理几个线程。
线程调度的细节取决于底层平台,当Running的线程调用其yield方法或失去CPU资源的时候,即回到Runnable状态。
Blocked
当发生如下情况,线程会被阻塞/重新进入Runnable状态:
1. 线程调用sleep等可中断方法 ===> sleep方法经过指定时间/其它线程调用了阻塞线程的interrupt方法
2. 线程调用了一个阻塞式IO ===> 调用的阻塞式IO方法返回
3. 试图获取一个正被使用的同步锁 ===> 成功获取同步锁
4. 调用了wait/await方法 ===> 其它线程发出了notify/notifyAll/signal/signalAll
5. 调用了其它线程的join方法,进入阻塞 ===> 调用了join线程的线程执行体完成或死亡
6. 线程调用suspend方法(容易导致死锁,不建议使用) ===> 被调用了resume方法
Dead
当发生如下情况,线程结束
1. 线程执行体完成
2. 抛出未捕获的异常或错误
3. 直接调用stop方法(容易导致死锁,不建议使用)
可通过调用线程对象的isAlive方法,如果处于新建和死亡状态会返回false
线程管理
常用的线程管理包括设置后台线程、设置优先级、join、sleep、yield、以及interrupt
设置后台线程
setDaemon(true):设置为后台线程
isDaemon():用于判断指定线程是否为后台线程
设置优先级
setPriority(int priority):设置优先级
getPriority():获取优先级
public class ThreadPriority {
public static void main(String[] args) {
Thread t1 = new Thread(()->
{
while(true) {
System.out.println("t11111");
}
}, "t1");
t1.setPriority(Thread.NORM_PRIORITY);
Thread t2 = new Thread(()->{
while (true) {
System.out.println("t22222");
}
});
t2.setPriority(10);
t1.start();
t2.start();
}
}
join
在某个程序执行流中(如main线程),调用其它线程的join方法,调用线程(如main线程)将被阻塞,直到被join方法加入的线程执行完为止。
join线程可以理解为把一个问题分为若干小问题由不同的线程处理,在其它线程处理完毕后,再回到运行状态的概念。
public class ThreadJoin {
private static void shortSleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static Thread create(int seq) {
return new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "#" + i);
shortSleep();
}
}, String.valueOf(seq));
}
public static void main(String[] args) throws InterruptedException {
List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
threads.forEach(Thread::start);
//在main线程中, 依次调用thread的join方法, main会进入阻塞, 等其它线程完成了再行继续
for(Thread thread : threads) {
thread.join();
}
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "#" + i);
shortSleep();
}
}
}
sleep
Thread类的静态方法,用于暂停线程的执行。一般建议使用1.5后新增的TimeUnit类来更好的暂停线程
public class ThreadSleep {
private static void sleep(int ms) {
try {
TimeUnit.SECONDS.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(() ->
{
long startTime = System.currentTimeMillis();
sleep(2);
long endTime = System.currentTimeMillis();
System.out.println(String.format("Total spend %d second", (endTime - startTime)));
}).start();
/*
* Thread sleep times depends on your system
*/
long startTime = System.currentTimeMillis();
sleep(3);
long endTime = System.currentTimeMillis();
System.out.println(String.format("Main thread total spend %d second", (endTime - startTime)));
}
}
interrupt
interrupt方法,主要是另外一个线程用于打断一个阻塞状态的线程,使其重新进入Runnable状态。如果一个阻塞方法(如sleep、wait)可以被interrupt至Runnable状态,这些方法又叫“可中断方法”。interrupt包括3个方法:
1. void interrupt()
2. static boolean interrupted()
3. boolean isInterrupted()
public class ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
public void run() {
while(true) {
try {
/*
* sleep为可中断方法, 因此会擦除interrupt标志;
* 如果注释掉sleep方法, thread收到中断信号后,interrupt标志不会被移除
*/
TimeUnit.MINUTES.sleep(2);
} catch (InterruptedException e) {
System.out.printf("I am be interrupted? %s\n", isInterrupted());
}
}
}
};
thread.setDaemon(true);
thread.start();
//确保thread线程启动完毕
TimeUnit.SECONDS.sleep(2);
System.out.printf("I am be interrupted? %s\n", thread.isInterrupted());
//在main线程中, 调用线程对象thread的interrupt()方法, 中断thread线程的sleep阻塞状态.
thread.interrupt();
//确保thread线程进入Runnable并被线程调度器调动
TimeUnit.SECONDS.sleep(2);
System.out.printf("I am be interrupted? %s\n", thread.isInterrupted());
}
}
事实上,在Thread中维护了一个interrupt的flag,它对线程对象的影响在于:
1. 如果一个线程在运行过程中,另外一个线程调用interrupt方法,相当于似得interrupt的flag为true(上面的代码中,注释掉thread线程的sleep代码,可以看到28结果为true)
2. 如果一个线程因为可中断方法而导致的阻塞,另一个线程调用interrupt方法,阻塞线程捕获中断信号,会把interrupt的flag改为false,实现重置。
static boolean interrupted()方法除了判断线程是否中断外,会直接把interrupt的flag变为true。
public class ThreadInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if(Thread.interrupted()) {
System.out.println("Yes my interrupt flag has been removed!");
}else {
System.out.println("I am running");
}
}
});
thread.setDaemon(true);
thread.start();
//Main sleep to make sure thread start
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
//以下显示false
System.out.println("Main thread is interrupted? " + Thread.interrupted());
Thread.currentThread().interrupt();
//以下显示true
System.out.println("Main thread is interrupted? " + Thread.currentThread().isInterrupted());
try {
TimeUnit.MINUTES.sleep(2);
} catch (Exception e) {
System.out.println("I am be interrupted!!!!!!");
}
}
}
yield
与sleep方法不同,yield方法只是让当前线程暂停一下,以便线程调度器操作线程调度。该方法不会让线程进入阻塞
public class ThreadYield {
private static Thread create(int index) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new Thread(()->
{
if (index == 0) {
Thread.yield();
}
System.out.println(index);
});
}
public static void main(String[] args) {
IntStream.range(0, 2).mapToObj(ThreadYield::create).forEach(Thread::start);
}
}
线程关闭
线程关闭不建议使用stop方法,一般来说,线程关闭分为正常和异常两种情况。本文不讨论线程执行体正常完成退出以及异常抛出这两种情况,就业务逻辑中介绍两种主动关闭线程的方法。
1. 通过捕获interrupt中断信号来关闭线程
public class InterruptThreadExit {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("I will start to work");
//此处通过IsInterrupt或interrupted方法来判断中断状态, 以便控制while循环实现控制线程关闭的目的
while(!interrupted()) {
//System.out.println(isInterrupted());
}
System.out.println(isInterrupted());
System.out.println("I will exit");
}
};
t.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("ready to send interrupt signal");
t.interrupt();
}
}
2. 通过volatile设置关闭开关
更一般地,可以使用volatile的可见性特点,设置线程控制开关,来控制线程的关闭
/*
* 通过volatile修饰符, 设置开关来控制线程关闭
*/
public class FlagThreadExit { static class MyTask extends Thread{
//使用volatile修饰一个boolean开关变量
private volatile boolean closed = false;
public void run() {
System.out.println("I will start to work");
//通过开变量和中断标识, 以便控制while循环实现控制线程关闭的目的
while(!closed && !interrupted()) {
//working
}
System.out.println("I will exit");
}
//定义用于关闭的close方法, 设置开关变量和调用中断方法
public void close() {
closed = true;
interrupt();
}
} public static void main(String[] args) throws InterruptedException {
MyTask t = new MyTask();
t.start();
TimeUnit.SECONDS.sleep(2);
System.out.println("use close method");
t.close();
}
}
Java并发编程之线程生命周期、守护线程、优先级、关闭和join、sleep、yield、interrupt的更多相关文章
- Java多线程与并发——线程生命周期和线程池
线程生命周期: 线程池:是预先创建线程的一种技术.线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用.减少频繁的创建和销毁对象. java里面线程池的顶级接口是E ...
- Java并发编程(二)如何保证线程同时/交替执行
第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜.本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法. ...
- java多线程(2)---生命周期、线程通讯
java生命周期.线程通讯 一.生命周期 有关线程生命周期就要看下面这张图,围绕这张图讲解它的方法的含义,和不同方法间的区别. 1.yield()方法 yield()让当前正在运行的线程回到就绪 ...
- java并发编程JUC第九篇:CountDownLatch线程同步
在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...
- Java7并发编程实战(一) 守护线程的创建和运行
Java里有一种特殊的线程叫做守护(Daemon)线程,这种线程的优先级很低,通常来说,当一个应用程序里面没有其他线程运行的时候,守护线程才运行,当线程是程序中唯一运行的线程时,守护线程执行结束后,J ...
- java并发编程实战:第七章----取消与关闭
Java没有提供任何机制来安全地终止线程(虽然Thread.stop和suspend方法提供了这样的机制,但由于存在缺陷,因此应该避免使用 中断:一种协作机制,能够使一个线程终止另一个线程的当前工作 ...
- 《java并发编程实战》读书笔记6--取消与关闭
第7章 取消与关闭 这章的主要内容是关于如何使任务和线程安全,快速,可靠的停止下来. 7.1 任务取消 在Java中没有一种安全的抢占方式来停止线程,但是可以使用一些协作机制,比如: 让素数生成器运行 ...
- Java 并发编程中的 Executor 框架与线程池
Java 5 开始引入 Conccurent 软件包,提供完备的并发能力,对线程池有了更好的支持.其中,Executor 框架是最值得称道的. Executor框架是指java 5中引入的一系列并发库 ...
- Java并发编程(三)什么是线程池
什么是线程池 学习编程的小伙伴们会经常听到“线程池”.“连接池”这类的词语,可是到底“池”是什么意思呢?我讲个故事大家就理解了:在很久很久以前有一家银行,一年之中只有一个客户来办理业务,随着时间的推移 ...
- [Java并发编程之美]第1章 线程基础 补充知识
1.2线程创建与运行 创建线程有三种方式: 继承Thread类并重写run方法: 实现Runnable接口的run方法,new Thread时将该类对象作为参数传入: 实现Callable接口的cal ...
随机推荐
- WordPress函数wp_page_menu详解
说明 该标签显示带有链接的WordPress页面列表,并且可以选择将 Home(主页)自动显示为列表中的一员.该标签是自定义侧边栏和标题栏的好帮手,同时还可以用在其它模板中. WordPress教程 ...
- [环境配置]Ubuntu16.04下编译安装gcc6.3.0
上一篇的SVS要用gcc6.3编译,否则结果不正确,本来以为gcc很好装,结果发现用apt-get安装gcc6只能安装6.5版本,代码作者奇特的要求只能用gcc6.3,没办法只能用源码装了,期间碰见了 ...
- Hacknet 玩后感
这款游戏的主题是黑客模拟.玩家需要帮助雇主搞定各种乱七八糟的需求. 你需要使用各种工具和各种linux命令行来获取对方电脑的Root权限.然后就是各种增删改查... 解密部分设计的太过简单,导致玩的时 ...
- spring cloud 入门系列:总结
从我第一次接触Spring Cloud到现在已经有3个多月了,当时是在博客园里面注册了账号,并且看到很多文章都在谈论微服务,因此我就去了解了下,最终决定开始学习Spring Cloud.我在一款阅读A ...
- 百道Python入门级练习题(新手友好)第一回合——矩阵乘法
题目描述 [问题描述] 编写程序,完成3*4矩阵和4*3整数矩阵的乘法,输出结果矩阵. [输入形式] 一行,供24个整数.以先行后列顺序输入第一个矩阵,而后输入第二个矩阵. [输出形式] 先行后列顺序 ...
- Kafka科普系列 | Kafka中的事务是什么样子的?
事务,对于大家来说可能并不陌生,比如数据库事务.分布式事务,那么Kafka中的事务是什么样子的呢? 在说Kafka的事务之前,先要说一下Kafka中幂等的实现.幂等和事务是Kafka 0.11.0.0 ...
- 详细介绍redis的集群功能,带你了解真正意义上的分布式
Redis 集群是一个分布式(distributed).容错(fault-tolerant)的 Redis 实现, 集群可以使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset). ...
- Flink 部署文档
Flink 部署文档 1 先决条件 2 下载 Flink 二进制文件 3 配置 Flink 3.1 flink-conf.yaml 3.2 slaves 4 将配置好的 Flink 分发到其他节点 5 ...
- spring boot+mybatis+swagger搭建
环境概述 使用的开发工具:idea 2018 3.4 环境:jdk1.8 数据库:MariaDB (10.2.21) 包管理:Maven 3.5 Web容器:Tomcat 8.0 开发机系统:Wind ...
- lastlog命令详解
基础命令学习目录首页 原文链接:https://www.cnblogs.com/qiyebao/p/4331078.html last 显示所有用户最后登录信息(会显示系统用户) last -u 50 ...