前言

本文目的,通过短小精悍的实例,让你在最短时间,全面揭晓 thread.setDaemon(true)守护线程的使用,及其使用场景。一看就懂,一学就会!

概述

守护线程的作用

用来让其(这里暂称之为子线程)随着调用它的主线程(这里暂称之为main方法)的结束而结束,不管该线程任务是否圆满完成,只要调用它的主线程结束了,它(子线程)就跟随着结束。

拿老板和员工举例:

给员工设置了守护线程,就意味着只要老板(主线程)“休假”(工作完成,没事可做了),员工(子线程)也跟着休假不用上班,无论员工手头的活干完没有;

没给员工设置守护线程,就意味着,即使老板“休假”了,员工依然要在后台继续完成它可能永远都完不成的工作!

一、代码示例

/*** 类描述:了解守护线程的用法* 守护线程作用:为了确保调用它的主方法结束的同时子线程也一并结束(并不一定是进入死亡,有可能仅仅进入了等待),就需要设置为守护线程。否则,可能会产出主线程结束后,子线程依然在继续运行* 例如:thread1.setDaemon(true);默认是false,没有开启守护线程*/
public class ThreadDaemon {public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("----我是子线程,每隔一秒打印一次,就是玩儿-----");}});thread.setDaemon(true);//true 确保主方法结束时候,子线程随之结束(默认是false)thread.start();try {TimeUnit.SECONDS.sleep(3);//让主线程休息一会儿,给子线程充足的执行时间,否则可能造成因主线程执行速度过快,主线程结束了子线程还没来得及执行} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程已结束");//只有new(新创建)和terminated(死亡)状态的线程下的isAlive()返回值才是false//其他waiting、blocked、runable、running状态下,isAlive()返回值都是trueSystem.out.println("子线程状态:"+thread.getState());System.out.println("是否依然存活:"+thread.isAlive());System.out.println("是否是守护线程:"+thread.isDaemon());}
}

二、运行结果

1. thread.setDaemon(true)时的运行结果

thread.setDaemon(true);//true 确保主方法结束时候,子线程随之结束(默认是false)

可以看到,main方法的主线程结束的同时,子线程就跟着屁颠的结束了(没有嚷嚷着说,我是无限for循环,我就不停) ,这个也就是thread.setDaemon(true)的意义所在。

1.需要注意,子线程休息的状态可能是TIMED_WAITING、RUNNING、BLOCKED三种状态,具体进入那种状态是有jvm来决定的

经过反复实验,如上图所示:

设置守护线程后,线程随着主线程运行结束后,不是每次都会进入wait等待队列(包含:BLOCKED、TIMED_WAITING),有时候也会直接进入runnable就绪队列。

注:等待(wait)队列就是阻塞(blocked)队列,没有被CPU直接执行的权利,只有在runnable就绪队列就绪队列才会有被CPU执行的权利(貌似在说我准备好了,CPU你来执行我吧)。

2.设置为守护线程可能存在的问题

设置守护线程为true后,子线程(内部有短时间难以干完的活)可能会因主线程的结束而进入等待(大部分情况下,子线程活如果干完了,会直接进入terminated死亡状态),可能会直接导致子线程自己的活儿没干完,而被暂停!(有时候它的活可能永远干不完,不能休息进入waiting,本例的for死循环就是这个意思)。

简而言之:被设置守护的子线程,如果子线程内部是简单的工作,不会造成影响,直接会随着主线程的结束而彻底结束,如果子线程内部是死循环,子线程会随着主线程的结束而暂停,进入等待队列或者就绪队列,但是有可能剩余的工作就无法再继续。

2. thread.setDaemon(false)时的运行结果

//thread.setDaemon(true);//true 确保主方法结束时候,子线程随之结束(默认是false)

主线程main方法在休息3秒后,就宣告结束了,但是,子线程自己依然在不知疲倦的一直在执行自己的业务代码 。

这个就是默认情况下,thread多线程默认thread.setDaemon(false)的样子,当然之所以是这样因为子线程run方法内部是死循环,如果是简单的一行打印,则子线程直接跟着主线程彻底消亡,不会持续打印。

3.关于thread.isAlive()返回值的一点反思

当子线程被设置为守护线程时,子线程随着主线程的结束而结束,子线程的isAlived()不应该是false吗?

通过thread.getState(),得到的结果是TIMED_WAITING,可以看出,子线程运行结束后,它并没有真正意义上的死亡,仅仅是进入了等待状态(本例比较特殊,是死循环,其他正常情况下,子线程会进入terminated死亡状态)。

1、线程的isAlived(),只有在new新建状态和terminated死亡状态的返回值才是false;

2、线程的isAlived(),在waiting、blocked、runnable、running状态下,返回值都是true。

1.什么时候线程的state状态会变成TERMINATED?isAlive()返回值变为false

看完上面的示例,你可能会有疑惑,在本例中,无论设置守护线程与否,thread.isAlive()调用的最终结果一直都是ture。

心里一万个草拟马!!!这是为什么呢?下面一起来彻底揭秘一下。

public class ThreadStateDemo implements Runnable{@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(1);System.out.println("哈哈哈");} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) throws InterruptedException {ThreadStateDemo demo=new ThreadStateDemo();Thread t=new Thread(demo,"demo");System.out.println(t.getState());t.start();System.out.println(t.getState());TimeUnit.SECONDS.sleep(3);System.out.println(t.getState());System.out.println(t.isAlive());}
}

运行结果如下:

反思

第一个示例中,之所以无论设置守护线程与否,子线程的最终isAlive()返回值都是true,意味着线程并没有消亡,依然是活的。

这究竟是什么原因呢?因为第一个示例中的run方法内部是一个死循环,会无休止的每隔一秒冒个泡,打印一句话。

所以,设置为守护线程后,它虽然暂时随着主线程歇业了,但是子线程并不死心,依然惦记着自己的死循环,自己的任务,所以只是暂时进入了假死,进入TIMED_WAITING或者RUNNABLE状态,并没有进入TERMINATED死亡状态。

第二个示例中,无论设置守护线程与否,子线程的最终归宿的是TERMINATED状态,这又是为什么呢?是因为子线程内部的run方法,任务完成了,没有遗留工作需要完成,也没有什么死循环,所以生命就此就截止了,进入了TERMINATED状态。

至此,心中的所有疑惑解开!!

三、thread.setDaemon(true)的业务场景

1、当且仅当你希望调用它的线程(可能是主线程或其他线程)结束而结束,不在意子线程的任务是否圆满完成时,此时可以使用守护线程。

2、当你希望在调用它的线程结束时,被调用的子线程继续运行的时候,就不需要设置守护线程。

四、守护线程到底是不是一个线程

在学习过程中,发现有人在博文中说守护线程也是一个线程,甚至示例也存在问题,误认为守护线程会开启一个新线程,独自作为一个新线程而存在,这样是不严谨,甚至是错误的。

守护线程不要误认为它是一个线程,它的存在需要依附于一个已存在的线程(暂时叫他thread),然后给这个已存在的线程通过thread.setDaemon(true),把这个已存在的线程设置为守护线程。

守护线程并不能脱离已有线程,单独存在,所以严格意义上,它并不是一个严格意义上的线程。

也就是说,人家本来就是一个线程,不能因为你把它设置为守护线程,或者没有设置为守护线程,它就是一个线程或者不是一个线程了,对吧!

总之,你理解就可以,在理解的基础上,你一定要继续说它是线程,我也不反驳,理解就好。

反例:如果守护线程是一个线程,那么给一个已存在的thread,设置为守护线程后,就应该多出一个线程,存在2个线程,对吧!继续看关于守护线程的实验!

4.1 守护线程是否会开启新的线程示例

1.代码片段

public class ThreadActiveCountTest {public static void main(String[] args) throws InterruptedException {System.out.println("活跃线程的数量:"+Thread.activeCount());   //此时打印的1,是main方法的线程System.out.println(Thread.currentThread().getName());;Thread thread=new Thread();System.out.println("活跃线程的数量:"+Thread.activeCount());  //因为new的线程没有start,所以活跃的线程仍然是1thread.setDaemon(true);//该参数设置为true或false,并不影响活跃线程的数量,它仅影响主线程退出时,子线程是否跟随停止运行(进入等待或就绪队列)System.out.println("活跃线程的数量:"+Thread.activeCount());    //因为new的线程没有start,所以活跃的线程仍然是1thread.start();System.out.println("活跃线程的数量:"+Thread.activeCount());   //因为new的线程已start,所以活跃的线程变为2Thread.sleep(5*1000);//给足子线程充足的执行时间System.out.println("最终活跃线程的数量:"+Thread.activeCount());}
}

2.运行结果

经过反复实验,无论是把thread.setDaemon(true)还是thread.setDaemon(false),最终的运行结果,可以发现,一个线程无论是否开启守护线程,并不会增加线程的数量,所以守护线程不是一个真正意义上的线程,它仅起到给一个已存在线程锦上添花的作用(把已存在线程,标记为守护线程)。

五、 关于Thread.activeCount()的个数的反思

你可能很好奇,为什么会问守护线程是线程吗?这样的傻逼问题。根源在下面这段代码!有人把它当做会开启新的线程,而写出错误的代码,导致代码不会执行,相信也有其他人会犯这个错误。故在此分享一下


/*** 关于死锁的一个测试* 如何避免:尽可能共用一把锁;或者两把锁不要混在一起使用,把两把锁lock/unlock分开写,不要混在一起*/
public class ReentrantLockDeadLockTest {static Lock lock1 = new ReentrantLock();static Lock lock2 = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(new DeadLockDemo(lock1, lock2), "Thread1");Thread thread2 = new Thread(new DeadLockDemo(lock2, lock1), "Thread2");thread1.start();thread2.start();Thread.sleep(5 * 1000);//主线程休息一会儿,给子线程充足的执行时间if (Thread.activeCount() >=4) {//注意:这个是4是错误的thread1.interrupt();//让thread1线程中断}Thread.sleep(5 * 1000);//主线程再休息一会儿,给子线程充足的阻断时间System.out.println("活跃线程的个数:"+Thread.activeCount());//最后打印活跃个数}static class DeadLockDemo implements Runnable {Lock lockA;Lock lockB;public DeadLockDemo(Lock lockA, Lock lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {try {//lockA.lock();lockA.lockInterruptibly();System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockA + "\t 尝试获得:" + lockB);TimeUnit.SECONDS.sleep(2);//lockB.lock();lockB.lockInterruptibly();System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockB + "\t 尝试获得:" + lockA);} catch (InterruptedException e) {e.printStackTrace();} finally {lockA.unlock();lockB.unlock();System.out.println(Thread.currentThread().getName() + "正常结束!");}}}
}

相信会有人,不知道Thread.activeCount()后面的数字,到底该怎么断定!!

先说答案,上面的代码是错误的Thread.activeCount()>=4,永远不成立,因为最多只有3个线程,永远也不会>=4,所以其中的thread1.interrupt()永远也不会执行,就会造成死锁。

这里判断的目的是判断是否还有子线程在运行,那么这里直接写>1就可以了,那个1就是main方法的活跃线程,除了main剩下的都是子线程了,所以应该是>1,而不是>=4。

引申:如何避免死锁?

1.尽可能共用一把锁;或者两把锁不要混在一起使用,把两把锁lock/unlock分开写,不要混在一起。

2.通过把lock.lock(),修改为lock.lockInterruptibly(),然后通过一定条件判断是否死锁,进而决定是否调用interrupt()来进行阻断。当然这样处理死锁,并不是特别好的方法,万一线程真的是执行了很久,而不是死锁了,如果贸然中断,可不是一个明智的处理方法。

3.ReentrantLock 提供了一个tryLock(参数) 方法,可以指定获取锁的等待时间。
tryLock()如果拿到锁就返回true,否则返回false,不会像lock那样无限等待。

if (!lockA.tryLock(2, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + " 正在等待锁......");
} else {System.out.println(Thread.currentThread().getName() + " 拿到了锁");
}

5.1 日常开发中,怎么断定Thread.activeCount()的个数呢?

通过上述案例,已经可以简单的判断活跃线程的个数了,下面继续深入探讨一下

首先main方法身算一个;

其次,每new一个Thread对象,并且让这个thread调用start()后,这个活跃个数就会增加1。如果一个for循环创建了100个线程,都开启了start(),那么activeCount()就会逐步增加到1+100。

如果这些新建的线程的run方法内部,很简单没有死循环,那么他们都会跟随这主线程main方法的运行结束,而消亡,最终activeCount()的数量,只剩下main方法的1

如果这些新建的线程run方法内部是死循环,则无论你是否给新建的线程设置守护线程,这些线程都不会消亡,最终activeCount()的数量,就是main方法的1+这些线程的总个数100。

预告:

活跃线程的数量和是否设置为守护线程无关,守护线程不会增加线程,乃至活跃线程的数量。最终活跃线程的多少,和锁运行子线程是否结束有关,换言之和子线程内部是否有大量的难以在很短内执行完毕的代码或者是否有死循环有直接关系。

上示例,让示例说话吧!

5.2 示例1:run内部没有死循环,会随着主线程的结束而消亡

1.代码片段

public class ThreadActiveCountA extends Thread {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(1);//为了防止打印过快,让其休息1秒打印一次System.out.println("我是测试类A,没有死循环,就是玩儿");} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) throws InterruptedException {System.out.println("活跃线程的个数:" + Thread.activeCount());Thread thread = null;for (int i = 0; i < 5; i++) {thread = new ThreadActiveCountA();thread.setDaemon(true);thread.start();System.out.println("内部打印--活跃线程的个数:" + Thread.activeCount());}//注:这里也可以通过判断Thread.activeCount()个数,来让主线程Thread.yield();为了更好演示效果,使用下面的sleep()Thread.sleep(10 * 1000);//让主线程多休息会,保障子线程执行完毕System.out.println("活跃线程的个数:" + Thread.activeCount());//最终的个数只会是1,那就是主线程的}
}

2.运行结果

肉眼可见,随着子线程的start(),活跃个数也是随着逐个增加的!

子线程执行完毕,进入死亡状态,最后仅仅剩下了主线程,活跃个数1

3.小节

在本案例中,无论你在main方法中是否把new的线程,设置为thread.setDaemon(true)/thread.setDaemon(false)守护线程,如上图所示,都不会影响最终的打印结果,更不会因为设置了守护线程,而多出一些线程!

主线程1+for循环生成的5个线程,活跃线程个数的最高峰是1+(1+1+1+1+1)=6,而不是1+5+5=11,最终子线程game over 进入terminated死亡状态(不再活跃),所以只剩下了主线的1。

注:算式中,前一个5是main方法中new的5个子线程,后一个5是意淫的设置为守护线程中会分别多出5个守护线程(这种想法明细是错误的)

在实际的开发中,往往不会这样new出多个Thread,而是使用线程池(ThreadPoolExecutor、ForkJoinPool、Executors、自定义线程池等)。

5.3.示例2:run内部有死循环,子线程不会随着主线程的结束而消亡

1.代码片段

import java.util.concurrent.TimeUnit;public class ThreadActiveCountB extends Thread  {@Overridepublic void run() {synchronized (this) {while (true) {try {TimeUnit.SECONDS.sleep(3);//为了防止打印过快,让其休息3秒打印一次System.out.println("我是测试类B,我的run方法是一个死的for循环,就是打印着玩儿");} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) throws InterruptedException {System.out.println("活跃线程的个数:" + Thread.activeCount());Thread thread = null;//在main中开启5个子线程for (int i = 0; i < 5; i++) {thread = new ThreadActiveCountB();//注意:这里是B类thread.setDaemon(true);//true设置为守护线程,默认是falsethread.start();System.out.println("内部打印--活跃线程的个数:" + Thread.activeCount());}//注:这里也可以通过判断Thread.activeCount()个数,来让主线程Thread.yield();为了更好演示效果,使用下面的sleep()Thread.sleep(10 * 1000);//让主线程多休息会,保障子线程执行完毕System.out.println("活跃线程的个数:" + Thread.activeCount());//无论设置守护线程与否,最终打印结果都是1+5=6}
}

2.运行结果

因为内部有死的for循环,是否设置守护线程,并不影响最终线程的活跃个数,区别仅仅是死循环是否在主线程结束后,依然在持续打印。

3.小节

在该案例中,线程类B的内部是一个死循环,无论在main方法中是否给子线程设置守护线程,最终活跃线程的个数依然是1+5=6,并没有出现1+5+5=11。

或许有人疑惑,为什么是6?

因为上面一开始在Thread.activeCount()>=4,引发的血案中,已经提及,设置守护线程只能让子线程随着主线程的停止而停止运行,决定子线程是否死亡的(是否在主线程结束后,依然是存活),并不是主线程,而是子线程所归属类中的run方法内部,是否有一个死循环或者难以在短时间内执行完毕的代码块。

如果存在“短时间内难以执行完毕的代码,包括死循环”,无论是否设置为守护线程,无论主线程是否结束,其子线程都会处于alive状态,而不是死亡状态。

5.4 小节

经过一番论述+案例展示,发现是否设置守护线程,并不会影响原线程的个数,也就是说守护线程它仅仅是一个概念的存在,需要依附于已有线程,并不会因为是否设置守护线程,就会在已有线程基础上,多了或者少了线程。

至此,我只是说,守护线程不是严格意义上的线程,不会因为它的存在(是否设置守护线程)而开启新的线程。你能懂就好,你说他就是线程,我依然不反驳,理解、会用就好。

至此,我想我应该把守护线程怎么用、是否是线程,说清楚了。相信此时的你已经会使用Thread.activeCount()了, 如有疑问一起留言探讨吧!

尾言

多线程的学习,就像一次修行,知识点甚多,想学好多线程不是一蹴而就的,需要耐心慢慢深耕,还好有我作伴,一起来探索吧!

附注

猜你还可能会以下内容感兴趣

1、JAVA多线程:synchronized理论和用法 | Lock和ReentrantLock Volatile 区别和联系(一)

2、JAVA多线程:yield/join/wait/notify/notifyAll等方法的作用(二)

3、JAVA多线程:狂抓!join()方法到底会不会释放锁,给你彻底介绍清楚(三)

4、JAVA多线程:sleep(0)、sleep(1)、sleep(1000)的区别(四)

5、JAVA多线程:揭秘thread.setDaemon(true) | thread.isAlive守护线程的使用(五)

JAVA多线程:守护线程 setDaemon全方位剖析| 守护线程是线程吗 |thread.isAlive()反思(五)相关推荐

  1. Java多线程闲聊(四):阻塞队列与线程池原理

    Java多线程闲聊(四)-阻塞队列与线程池原理 前言 复用永远是人们永恒的主题,这能让我们更好地避免重复制造轮子. 说到多线程,果然还是绕不开线程池,那就来聊聊吧. 人们往往相信,世界是存在一些规律的 ...

  2. Java多线程编程-(6)-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    前几篇: Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地Th ...

  3. 【Java之多线程(二)】(***重要***)Java多线程中常见方法的区别,如object.wait()和Thread.sleep()的区别等

    1.Java中Thread和Runnable的区别??? 区别: 在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处: 避免点 ...

  4. java thread isalive,《Java多线程编程核心技术(第2版)》 —1.4 isAlive()方法

    1.4 isAlive()方法 isAlive()方法的功能是判断当前的线程是否存活. 新建项目t7,类文件MyThread.java代码如下: public class MyThread exten ...

  5. java 关闭守护线程_Java并发编程之线程生命周期、守护线程、优先级、关闭和join、sleep、yield、interrupt...

    Java并发编程中,其中一个难点是对线程生命周期的理解,和多种线程控制方法.线程沟通方法的灵活运用.这些方法和概念之间彼此联系紧密,共同构成了Java并发编程基石之一. Java线程的生命周期 Jav ...

  6. Java多线程:synchronized | Volatile 和Lock和ReadWriteLock多方位剖析(一)

    前言 本文站在多线程初中级学习者的角度,较为全面系统的带你一起了解多线程与锁相关的知识点.带你一起解开与锁相关的各种概念.用法.利弊等.比如:synchronized.Volatile.Lock.Re ...

  7. Java 多线程:彻底搞懂线程池

    熟悉 Java 多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了. 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 ...

  8. 【Java系列】(四)Java多线程---线程安全

    前言: 记得大一刚学Java的时候,老师带着我们做了一个局域网聊天室,用到了AWT.Socket.多线程.I/O,编写的客户端和服务器,当时做出来很兴奋,回学校给同学们演示,感觉自己好NB,呵呵,扯远 ...

  9. Java多线程1(线程创建)

    一 线程与进程 什么是进程? 电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的.比如下图中的QQ.酷狗播放器.电脑管家等等.在这里插入图片描述 什么是线程? 进程想 ...

最新文章

  1. 为什么python打开pygame秒关闭后在运行_当我关闭Pygame时屏幕冻结
  2. 观念什么意思_俗语“女怕午时生,男怕子夜临”是啥意思?古人的忌讳有道理吗?...
  3. Python 学习笔记(2)字典默认值和集合的操作
  4. 数据结构与算法-ADT-Array
  5. 微信公众平台开发(150)——从新浪云SAE上传图片到图文消息
  6. Delphi一句话帮助
  7. spark读取kafka数据_解决Spark数据倾斜(Data Skew)的N种姿势
  8. Linux编译安装PHP7.4.24及启动
  9. 解只含加减的一元一次方程
  10. bi 工具 市场排行榜_现在市场上的主流BI工具有哪几个
  11. alienware Win8 系统安装
  12. spring cloud 解决问题
  13. 微信公众号关注/取消关注事件推送开发记录
  14. css控制文本只显示两行
  15. ❤️UNITY实战进阶-OBB包围盒详解-6
  16. otf和ctf的意义_北京邮电大学出版社
  17. 5G的NSA和SA,到底啥意思?
  18. 晕菜了,TFS居然把vss里的那个rollback功能cut掉了,还好有人写了工具.
  19. Vue.js入门指南(一)
  20. 网络流量大数据分析平台(1)

热门文章

  1. Openstack容器项目之Magnum
  2. python模糊路径读取文件
  3. Android消息推送之Androidpn_Demo版到正式上线
  4. Java对性别默认值为男_当对象或对象属性为空时,如何安全给对象或对象属性添加默认值...
  5. Unity测量工具——可视化直尺测量两点距离,并显示实时测量数据
  6. 如何把计算机颜色调正常,怎样调电脑屏幕亮度和颜色,电脑屏幕颜色调回正常...
  7. golang汉字转拼音字头和五笔码
  8. excel 2007数据透视表教程
  9. UI设计中的排版方法
  10. 通信系列1: 电话的前世今生