额!Java中用户线程和守护线程区别这么大?
在 Java 语言中线程分为两类:用户线程和守护线程,而二者之间的区别却鲜有人知,所以本文磊哥带你来看二者之间的区别,以及守护线程需要注意的一些事项。
1.默认用户线程
Java 语言中无论是线程还是线程池,默认都是用户线程,因此用户线程也被成为普通线程。
以线程为例,想要查看线程是否为守护线程只需通过调用 isDaemon()
方法查询即可,如果查询的值为 false
则表示不为守护线程,自然也就属于用户线程了,如下代码所示:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是子线程");
}
});
System.out.println("子线程==守护线程:" + thread.isDaemon());
System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());
}
以上程序的执行结果为:
从上述结果可以看出,默认情况下主线程和创建的新线程都为用户线程。
PS:Thread.currentThread() 的意思是获取执行当前代码的线程实例。
2.主动修改为守护线程
守护线程(Daemon Thread)也被称之为后台线程或服务线程,守护线程是为用户线程服务的,当程序中的用户线程全部执行结束之后,守护线程也会跟随结束。
守护线程的角色就像“服务员”,而用户线程的角色就像“顾客”,当“顾客”全部走了之后(全部执行结束),那“服务员”(守护线程)也就没有了存在的意义,所以当一个程序中的全部用户线程都结束执行之后,那么无论守护线程是否还在工作都会随着用户线程一块结束,整个程序也会随之结束运行。
那如何将默认的用户线程修改为守护线程呢?
这个问题要分为两种情况来回答,首先如果是线程,则可以通过设置 setDaemon(true)
方法将用户线程直接修改为守护线程,而如果是线程池则需要通过 ThreadFactory
将线程池中的每个线程都为守护线程才行,接下来我们分别来实现一下。
2.1 设置线程为守护线程
如果使用的是线程,可以通过 setDaemon(true)
方法将线程类型更改为守护线程,如下代码所示:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是子线程");
}
});
// 设置子线程为守护线程
thread.setDaemon(true);
System.out.println("子线程==守护线程:" + thread.isDaemon());
System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());
}
以上程序的执行结果为:
2.2 设置线程池为守护线程
要把线程池设置为守护线程相对来说麻烦一些,需要将线程池中的所有线程都设置成守护线程,这个时候就需要使用 ThreadFactory
来定义线程池中每个线程的线程类型了,具体实现代码如下:
// 创建固定个数的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// 设置线程为守护线程
t.setDaemon(false);
return t;
}
});
如下图所示:
如上图所示,可以看出,整个程序中有 10 个守护线程都是我创建的。其他几种创建线程池的设置方式类似,都是通过 ThreadFactory
统一设置的,这里就不一一列举了。
3.守护线程 VS 用户线程
通过前面的学习我们可以创建两种不同的线程类型了,那二者有什么差异呢?接下来我们使用一个小示例来看一下。
下面我们创建一个线程,分别将这个线程设置为用户线程和守护线程,在每个线程中执行一个 for
循环,总共执行 10 次信息打印,每次打印之后休眠 100 毫秒,来观察程序的运行结果。
3.1 用户线程
新建的线程默认就是用户线程,因此我们无需对线程进行任何特殊的处理,执行 for
循环即可(总共执行 10 次信息打印,每次打印之后休眠 100 毫秒),实现代码如下:
/**
* Author:Java中文社群
*/
public class DaemonExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 打印 i 信息
System.out.println("i:" + i);
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 启动线程
thread.start();
}
}
以上程序执行结果如下:
从上述结果可以看出,当程序执行完 10 次打印之后才会正常结束进程。
3.2 守护线程
/**
* Author:Java中文社群
*/
public class DaemonExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 打印 i 信息
System.out.println("i:" + i);
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 设置为守护线程
thread.setDaemon(true);
// 启动线程
thread.start();
}
}
以上程序执行结果如下:
从上述结果可以看出,当线程设置为守护线程之后,整个程序不会等守护线程 for
循环 10 次之后再进行关闭,而是当主线程结束之后,守护线程只执行了一次循环就结束运行了,由此可以看出守护线程和用户线程的不同。
3.3 小结
守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行完成之后程序就会结束运行,程序结束运行时不会管守护线程是否正在运行,由此我们可以看出守护线程在 Java 体系中权重是比较低的。
4.守护线程注意事项
守护线程的使用需要注意以下三个问题:
- 守护线程的设置
setDaemon(true)
必须要放在线程的start()
之前,否则程序会报错。 - 在守护线程中创建的所有子线程都是守护线程。
- 使用
jojn()
方法会等待一个线程执行完,无论此线程是用户线程还是守护线程。
接下来我们分别演示一下,以上的注意事项。
4.1 setDaemon 执行顺序
当我们将 setDaemon(true)
设置在 start()
之后,如下代码所示:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 打印 i 信息
System.out.println("i:" + i + ",isDaemon:" +
Thread.currentThread().isDaemon());
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 启动线程
thread.start();
// 设置为守护线程
thread.setDaemon(true);
}
以上程序执行结果如下:
从上述结果可以看出,当我们将 setDaemon(true)
设置在 start()
之后,不但程序的执行会报错,而且设置的守护线程也不会生效。
4.2 守护线程的子线程
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
}
});
System.out.println("守护线程的子线程 thread2 isDaemon:" +
thread2.isDaemon());
}
});
// 设置为守护线程
thread.setDaemon(true);
// 启动线程
thread.start();
Thread.sleep(1000);
}
以上程序执行结果如下:
从上述结果可以看出,守护线程中创建的子线程,默认情况下也属于守护线程。
4.3 join 与守护线程
通过 3.2 部分的内容我们可以看出,默认情况下程序结束并不会等待守护线程执行完,而当我们调用线程的等待方法 join()
时,执行的结果就会和 3.2 的结果有所不同,下面我们一起来看吧,示例代码如下:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 打印 i 信息
System.out.println("i:" + i);
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 设置为守护线程
thread.setDaemon(true);
// 启动线程
thread.start();
// 等待线程执行完
thread.join();
System.out.println("子线程==守护线程:" + thread.isDaemon());
System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());
}
以上程序执行结果如下:
通过上述结果我们可以看出,即使是守护线程,当程序中调用 join()
方法时,程序依然会等待守护线程执行完成之后再结束进程。
5.守护线程应用场景
守护线程的典型应用场景就是垃圾回收线程,当然还有一些场景也非常适合使用守护线程,比如服务器端的健康检测功能,对于一个服务器来说健康检测功能属于非核心非主流的服务业务,像这种为了主要业务服务的业务功能就非常合适使用守护线程,当程序中的主要业务都执行完成之后,服务业务也会跟随者一起销毁。
6.守护线程的执行优先级
首先来说,线程的类型(用户线程或守护线程)并不影响线程执行的优先级,如下代码所示,定义一个用户线程和守护线程,分别执行 10 万次循环,通过观察最后的打印结果来确认线程类型对程序执行优先级的影响。
public class DaemonExample {
private static final int count = 100000;
public static void main(String[] args) throws InterruptedException {
// 定义任务
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("执行线程:" + Thread.currentThread().getName());
}
}
};
// 创建守护线程 t1
Thread t1 = new Thread(runnable, "t1");
// 设置为守护线程
t1.setDaemon(true);
// 启动线程
t1.start();
// 创建用户线程 t2
Thread t2 = new Thread(runnable, "t2");
// 启动线程
t2.start();
}
}
以上程序执行结果如下:
通过上述结果可以看出,线程的类型不管是守护线程还是用户线程对程序执行的优先级是没有任何影响的,而当我们将 t2
的优先级调整为最大时,整个程序的运行结果就完全不同了,如下代码所示:
public class DaemonExample {
private static final int count = 100000;
public static void main(String[] args) throws InterruptedException {
// 定义任务
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("执行线程:" + Thread.currentThread().getName());
}
}
};
// 创建守护线程 t1
Thread t1 = new Thread(runnable, "t1");
// 设置为守护线程
t1.setDaemon(true);
// 启动线程
t1.start();
// 创建用户线程 t2
Thread t2 = new Thread(runnable, "t2");
// 设置 t2 的优先级为最高
t2.setPriority(Thread.MAX_PRIORITY);
// 启动线程
t2.start();
}
}
以上程序执行结果如下:
通过上述的结果可以看出,程序的类型和程序执行的优先级是没有任何关系,当新创建的线程默认的优先级都是 5 时,无论是守护线程还是用户线程,它们执行的优先级都是相同的,当将二者的优先级设置不同时,执行的结果也会随之改变(优先级设置的越高,最早被执行的概率也越大)。
7.总结
在 Java 语言中线程分为用户线程和守护线程,守护线程是用来为用户线程服务的,当一个程序中的所有用户线程都结束之后,无论守护线程是否在工作都会跟随用户线程一起结束。守护线程从业务逻辑层面来看权重比较低,但对于线程调度器来说无论是守护线程还是用户线程,在优先级相同的情况下被执行的概率都是相同的。守护线程的经典使用场景是垃圾回收线程,守护线程中创建的线程默认情况下也都是守护线程。
关注公号「Java中文社群」查看更多有意思、涨知识的并发编程文章。
额!Java中用户线程和守护线程区别这么大?的更多相关文章
- Java用户线程和守护线程
今天看Java一个关于多线程返回值方式的示例,发现一个自己不太能理解的问题,就是在主线程中启动了几个工作线程,主线程中也没有join,工作线程居然也是正常输出了回调的结果.这个跟linux C++下的 ...
- Java中的守护线程 & 非守护线程(简介)
Java中的守护线程 & 非守护线程 守护线程 (Daemon Thread) 非守护线程,又称用户线程(User Thread) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守 ...
- 【java多线程】用户线程和守护线程的区别
java中线程分为两种类型:用户线程和守护线程.通过Thread.setDaemon(false)设置为用户线程:通过Thread.setDaemon(true)设置为守护线程.如果不设置次属性,默认 ...
- java高并发系列 - 第9天:用户线程和守护线程
守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程.JIT线程都是守护线程.与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作.如果 ...
- java 用户线程和守护线程
在Java中通常有两种线程:用户线程和守护线程(也被称为服务线程)通过Thread.setDaemon(false)设置为用户线程通过Thread.setDaemon(true)设置为守护线程线程属性 ...
- 进程?线程?多线程?同步?异步?守护线程?非守护线程(用户线程)?线程的几种状态?多线程中的方法join()?
1.进程?线程?多线程? 进程就是正在运行的程序,他是线程的集合. 线程是正在独立运行的一条执行路径. 多线程是为了提高程序的执行效率.2.同步?异步? 同步: 单线程 异步: 多线程 3.守护线程? ...
- java并发:初探用户线程和守护线程
用户线程和守护线程 用户线程 用户线程执行完,jvm退出.守护线程还是可以跑的 /** * A <i>thread</i> is a thread of execution i ...
- Java:多线程<四> Lock、停止线程、守护线程、join、优先级&yield
Java1.5以后,Condition将Object监视器方法(wait, notify, notifyAll)分解成截然不同的对象,以便通过这些对象与任意Lock实现组合使用为每个对像提供多个等待s ...
- JAVA并发实现四(守护线程和线程阻塞)
守护线程 Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程. 守护线程作用是为其他前台 ...
随机推荐
- 报错: You are using pip version 10.0.1, however version 18.0 is available.
报错: You are using pip version 10.0.1, however version 18.0 is available. You should consider upgradi ...
- 【转+】以C++为核心语言的高频交易系统的讨论
[前言]高频交易是量化交易的核心.主要分两个方向:计算机技术和交易策略.策略各有不同,一般都是数据分析的专家或者金融,机器学习从业者.在计算机技术方面,一个是交易平台的性能,二者是硬件的性能,延时的多 ...
- Serverless 2.0,鸡蛋还是银弹?
简介: 本篇旨在介绍 Serverless 如今应用到应用(非病句)的各种困境,以及帮助用户如何去规避一些问题,提前了解方向. 浪潮 从 2014 年 Serverless 冒头至今,已经有无数的勇士 ...
- 微信小程序折线图表折线图加区域图
1.先来个效果图 这里我用的是插件@antv/f2-canvas(安装的方法如下) npm init 此处如果直接使用官方npm install 可能会出现没有node_modules错误 npm i ...
- 剑指 Offer 56 - I. 数组中数字出现的次数 + 分组异或
剑指 Offer 56 - I. 数组中数字出现的次数 Offer_56_1 题目描述 解题思路 java代码 /** * 方法一:数位方法 */ class Offer_56_1_2 { publi ...
- Java 常见对象 04
常见对象·Arrays 类和 包装类 数组高级冒泡排序原理图解 * A:画图演示 * 需求: 数组元素:{24, 69, 80, 57, 13} 请对数组元素进行排序 * 冒泡排序: 相邻元素两两比较 ...
- Java开发工程师最新面试题库系列——Mybatis框架部分(附答案)
Mybatis Mybatis是什么框架? 答:持久层框架 Mybatis和ORM有什么区别? 答:ORM是对象关系映射的一种设计理念,也就是对象属性对应数据库字段,让开发人员以操作对象的方式操作数据 ...
- python带颜色打印字符串
python带颜色打印字符串 之前调试pwn题的时候,有时候需要将某些特别的,重要的信息用不一样的颜色打印出来.查阅一些资料,了解了print函数的特性后,自己写了一个脚本,可以用来获取带颜色信息的字 ...
- 【python+selenium的web自动化】- 控制浏览器的常用操作
如果想从头学起selenium,可以去看看这个系列的文章哦! https://www.cnblogs.com/miki-peng/category/1942527.html 前言 本文主要介绍se ...
- 一文吃透zabbix4.0的编译安装,最全最详细的安装。
什么是zabbix? zabbix作为一款企业级,开源的,分布式的监控套件,解决了以往监控软件的短板,可以说是现在流行的监控解决方案之一. 监控系统的理想化模样 1.监控数据收集及可视化. 2.数据要 ...