额!Java中用户线程和守护线程区别这么大?

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

在 Java 语言中线程分为两类:用户线程和守护线程,而二者之间的区别却鲜有人知,所以本文磊哥带你来看二者之间的区别,以及守护线程需要注意的一些事项。

1.默认用户线程

Java 语言中无论是线程还是线程池,默认都是用户线程,因此用户线程也被称为普通线程。

以线程为例,想要查看线程是否为守护线程只需通过调用 isDaemon() 方法查询即可,如果查询的值为 false 则表示不为守护线程,自然也就属于用户线程了,如下代码所示:

public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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.守护线程注意事项

守护线程的使用需要注意以下三个问题:

  1. 守护线程的设置 setDaemon(true) 必须要放在线程的 start() 之前,否则程序会报错。

  2. 在守护线程中创建的所有子线程都是守护线程。

  3. 使用 jojn() 方法会等待一个线程执行完,无论此线程是用户线程还是守护线程。

接下来我们分别演示一下,以上的注意事项。

4.1 setDaemon 执行顺序

当我们将 setDaemon(true) 设置在 start() 之后,如下代码所示:

public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {Thread thread2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void run() {for (int i = 0; i < count; i++) {System.out.println("执行线程:" + Thread.currentThread().getName());}}};// 创建守护线程 t1Thread t1 = new Thread(runnable, "t1");// 设置为守护线程t1.setDaemon(true);// 启动线程t1.start();// 创建用户线程 t2Thread 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() {@Overridepublic void run() {for (int i = 0; i < count; i++) {System.out.println("执行线程:" + Thread.currentThread().getName());}}};// 创建守护线程 t1Thread t1 = new Thread(runnable, "t1");// 设置为守护线程t1.setDaemon(true);// 启动线程t1.start();// 创建用户线程 t2Thread t2 = new Thread(runnable, "t2");// 设置 t2 的优先级为最高t2.setPriority(Thread.MAX_PRIORITY);// 启动线程t2.start();}
}

以上程序执行结果如下:



通过上述的结果可以看出,程序的类型和程序执行的优先级是没有任何关系,当新创建的线程默认的优先级都是 5 时,无论是守护线程还是用户线程,它们执行的优先级都是相同的,当将二者的优先级设置不同时,执行的结果也会随之改变(优先级设置的越高,最早被执行的概率也越大)。

7.总结

在 Java 语言中线程分为用户线程和守护线程,守护线程是用来为用户线程服务的,当一个程序中的所有用户线程都结束之后,无论守护线程是否在工作都会跟随用户线程一起结束。守护线程从业务逻辑层面来看权重比较低,但对于线程调度器来说无论是守护线程还是用户线程,在优先级相同的情况下被执行的概率都是相同的。守护线程的经典使用场景是垃圾回收线程,守护线程中创建的线程默认情况下也都是守护线程。

关注公号「Java中文社群」查看更多有意思、涨知识的并发编程文章。

原创不易,点个赞再走呗~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544832.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++中int向量初始化_以不同的方式在C ++中初始化2D向量

c中int向量初始化Prerequisite: Initialize 1D vector 先决条件&#xff1a; 初始化一维向量 Before discussing about the initialization techniques let us state what a 2D vector is. A 2D vector in simple sense is a matrix having rows and column. In other words, …

EasyUI-右键菜单变灰不可用效果

使用过EasyUI的朋友想必都知道疯狂秀才写的后台界面吧&#xff0c;作为一个初学者我不敢妄自评论它的好坏&#xff0c;不过它确实给我们提供了一个很好框架&#xff0c;只要在它的基础上进行修改&#xff0c;基本上都可以满足我们开发的需要。 知道“疯狂秀才”写的后台界面已经…

Oracle中insert into select和select into的区别

文章转自&#xff1a;http://www.linuxidc.com/Linux/2012-09/70984.htm 在Oracle中&#xff0c;将一张表的数据复制到另外一个对象中。通常会有这两种方法&#xff1a;insert into select 和 select into from。 前者可以将select 出来的N行(0到任意数)结果集复制一个新表中…

ThreadLocal中的3个大坑,内存泄露都是小儿科!

我在参加Code Review的时候不止一次听到有同学说&#xff1a;我写的这个上下文工具没问题&#xff0c;在线上跑了好久了。其实这种想法是有问题的&#xff0c;ThreadLocal写错难&#xff0c;但是用错就很容易&#xff0c;本文将会详细总结ThreadLocal容易用错的三个坑&#xff…

java中为按钮添加图片_如何在Java中为字符串添加双引号?

java中为按钮添加图片In Java, everything written in double-quotes is considered a string and the text written in double-quotes is display as it is. 在Java中&#xff0c; 双引号中的所有内容均视为字符串&#xff0c;而双引号中的文本按原样显示。 Suppose, if we wa…

基于.Net的单点登录(SSO)解决方案

为什么80%的码农都做不了架构师&#xff1f;>>> 前些天一位朋友要我帮忙做一单点登录&#xff0c;其实这个概念早已耳熟能详&#xff0c;但实际应用很少&#xff0c;难得最近轻闲&#xff0c;于是决定通过本文来详细描述一个SSO解决方案&#xff0c;希望对 大家有所…

Ajax在请求数据时显示等待动画遮罩

/*** 等待提醒 开始 *********************************************************************************************** -yzy -20150408 说明&#xff1a;在Ajax请求数据的时候&#xff0c;显示等待界面*/// 1、这里显示等待框$(#YWaitDialog).show();// 2、这里进行ajax的请…

c语言 函数的参数传递示例_isgreaterequal()函数以及C ++中的示例

c语言 函数的参数传递示例C isgreaterequal()函数 (C isgreaterequal() function) isgreaterequal() function is a library function of cmath header, it is used to check whether the given first value is greater than or equal to the second value. It accepts two va…

在android中ScrollView嵌套ScrollView解决方案

文章转载自&#xff1a;http://www.jb51.net/article/33054.htm大家好&#xff0c;众所周知&#xff0c;android里两个相同方向的ScrollView是不能嵌套的&#xff0c;那要是有这样的需求怎么办,接下来为您介绍解决方法&#xff0c;感兴趣的朋友可以了解下大家好&#xff0c;众所…

Cucumber 入门一

&#xff08;转自&#xff1a;http://www.cnblogs.com/jarodzz/archive/2012/07/02/2573014.html&#xff09; 第一次看到Cucumber和BDD&#xff08;Behavior Driven Development, 行为驱动开发&#xff09;&#xff0c;是在四年前。那时才開始工作&#xff0c;对软件測试工具相…

Python datetime astimezone()方法与示例

Python datetime.astimezone()方法 (Python datetime.astimezone() Method) datetime.astimezone() method is used to manipulate objects of datetime class of module datetime. datetime.astimezone()方法用于操作模块datetime的datetime类的对象。 It uses an instance …

Java中这7个方法,一不小心就用错了!

最近我们通过sonar静态代码检测&#xff0c;同时配合人工代码review&#xff0c;发现了项目中很多代码问题。除了常规的bug和安全漏洞之外&#xff0c;还有几处方法用法错误&#xff0c;引起了我极大的兴趣。我为什么会对这几个方法这么感兴趣呢&#xff1f;因为它们极具迷惑性…

java 标志一个方法为过时方法

使用 Deprecated 来标记方法 Deprecated//用来判断ip是否合法public boolean checkIp(String tempIp) {String regex "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)){3}"; // String regex2 "([1-9]|[1-9]\\\\d…

STL之顺序容器

顺序容器&#xff1a; vector&#xff1a;数组 list:链表 deque&#xff1a;双端数组 顺序容器适配器&#xff1a; stack&#xff1a;堆栈 queue&#xff1a;队列 priority_queue&#xff1a;优先级队列 deque是一个动态数组  deque与vector非常类似&#xff1b;  deque可以…

Java StringBuilder reverse()方法与示例

StringBuilder类reverse()方法 (StringBuilder Class reverse() method) reverse() method is available in java.lang package. reverse()方法在java.lang包中可用。 reverse() method is used to reverse this character sequence by the reverse of the sequence. reverse()…

这样设置,让你的 IDEA 好看到爆炸

今天这期我们来分享几个美化 IDEA 设置技巧&#xff0c;让你的 IDEA 与众不同。首先我们来看下 IDEA 默认设置&#xff0c;虽然不丑&#xff0c;但就是太单调&#xff0c;千篇一律。默认主题接着&#xff0c;我们来看下美化以后的界面&#xff0c;总体看起来是不是比默认好看了…

IMP-00002: 无法打开 D:\orcldat\test_20111024.dmp 进行读取,rman备份

文章转自&#xff1a;http://blog.csdn.net/wanglilin/article/details/6900633 首先&#xff0c;我的路径写错了&#xff0c;文件夹是orcldata我掉了个a。 其次&#xff0c;命令后添加 fully。 dos下随便哪个目录> [sql] view plaincopyprint? IMP username/pwddbname BU…

observable_Java Observable setChanged()方法与示例

observable可观察的类setChanged()方法 (Observable Class setChanged() method) setChanged() method is available in java.util package. setChanged()方法在java.util包中可用。 setChanged() method is used to set this Observable object status as changed. setChanged…

RabbitMQ 集群

2019独角兽企业重金招聘Python工程师标准>>> Clustering Guide A RabbitMQ broker is a logical grouping of one or several Erlang nodes, each running the RabbitMQ applicationand sharing users, virtual hosts, queues, exchanges, etc. Sometimes we refer …

使用uuid作为数据库主键,被技术总监怼了!

一、前言在日常开发中&#xff0c;数据库中主键id的生成方案&#xff0c;主要有三种数据库自增ID采用随机数生成不重复的ID采用jdk提供的uuid对于这三种方案&#xff0c;我发现在数据量少的情况下&#xff0c;没有特别的差异&#xff0c;但是当单表的数据量达到百万级以上时候&…