Java守护线程概述

欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。

欢迎跳转到本文的原文链接:https://honeypps.com/java/java-daemon-thread/

Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。

只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

首先看一个例子,主线程中建立一个守护线程,当主线程结束时,守护线程也跟着结束。

 

package com.daemon;

import java.util.concurrent.TimeUnit;

public class DaemonThreadTest
{
	public static void main(String[] args)
	{
		Thread mainThread = new Thread(new Runnable(){
			@Override
			public void run()
			{
				Thread childThread = new Thread(new ClildThread());
				childThread.setDaemon(true);
				childThread.start();
				System.out.println("I'm main thread...");
			}
		});
		mainThread.start();
	}
}

class ClildThread implements Runnable
{
	@Override
	public void run()
	{
		while(true)
		{
			System.out.println("I'm child thread..");
			try
			{
				TimeUnit.MILLISECONDS.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

运行结果:

 

 

I'm child thread..
I'm main thread...

如果不何止childThread为守护线程,当主线程结束时,childThread还在继续运行,如下:

package com.daemon;

import java.util.concurrent.TimeUnit;

public class DaemonThreadTest
{
	public static void main(String[] args)
	{
		Thread mainThread = new Thread(new Runnable(){
			@Override
			public void run()
			{
				Thread childThread = new Thread(new ClildThread());
				childThread.setDaemon(false);
				childThread.start();
				System.out.println("I'm main thread...");
			}
		});
		mainThread.start();
	}
}

class ClildThread implements Runnable
{
	@Override
	public void run()
	{
		while(true)
		{
			System.out.println("I'm child thread..");
			try
			{
				TimeUnit.MILLISECONDS.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

运行结果:

 

 

I'm main thread...
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..(无限输出)

可以看到,当主线程结束时,childThread是非守护线程,就会无限的执行。

 

守护线程有一个应用场景,就是当主线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要继续关闭子线程的麻烦。不过博主推荐,如果真有这种场景,还是用中断的方式实现比较合理。

还有补充一点,不是说当子线程是守护线程,主线程结束,子线程就跟着结束,这里的前提条件是:当前jvm应用实例中没有用户线程继续执行,如果有其他用户线程继续执行,那么后台线程不会中断,如下:

package com.daemon;

import java.util.concurrent.TimeUnit;

public class DaemonThreadTest
{
	public static void main(String[] args)
	{
		Thread mainThread = new Thread(new Runnable(){
			@Override
			public void run()
			{
				Thread childThread = new Thread(new ClildThread());
				childThread.setDaemon(true);
				childThread.start();
				System.out.println("I'm main thread...");
			}
		});
		mainThread.start();
		
		Thread otherThread = new Thread(new Runnable(){
			@Override
			public void run()
			{
				while(true)
				{
					System.out.println("I'm other user thread...");
					try
					{
						TimeUnit.MILLISECONDS.sleep(1000);
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				}
			}
		});
		otherThread.start();
	}
}

class ClildThread implements Runnable
{
	@Override
	public void run()
	{
		while(true)
		{
			System.out.println("I'm child thread..");
			try
			{
				TimeUnit.MILLISECONDS.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

运行结果:

I'm other user thread...
I'm child thread..
I'm main thread...
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm child thread..
I'm other user thread...
I'm other user thread...
I'm child thread..
I'm child thread..
I'm other user thread...
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..(无限输出)

如果需要在主线程结束时,将子线程结束掉,可以采用如下的中断方式:

 

 

package com.self;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadTest
{

	public static void main(String[] args)
	{
		Thread mainThread = new Thread(new Runnable(){
			public void run()
			{
				System.out.println("主线程开始...");
				Thread sonThread = new Thread(new Thread1(Thread.currentThread()));
				sonThread.setDaemon(false);
				sonThread.start();
				
				try
				{
					TimeUnit.MILLISECONDS.sleep(10000);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				System.out.println("主线程结束");
			}
		});
		mainThread.start();
	}
	
}

class Thread1 implements Runnable
{
	private Thread mainThread;
	
	public Thread1(Thread mainThread)
	{
		this.mainThread = mainThread;
	}
	
	@Override
	public void run()
	{
		while(mainThread.isAlive())
		{
			System.out.println("子线程运行中....");
			try
			{
				TimeUnit.MILLISECONDS.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
	
}

运行结果:

 

 

主线程开始...
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
子线程运行中....
主线程结束


回归正题,这里有几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。 
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。

 

 

 

写java多线程程序时,一般比较喜欢用java自带的多线程框架,比如ExecutorService,但是java的线程池会将守护线程转换为用户线程,所以如果要使用后台线程就不能用java的线程池。

如下,线程池中将daemon线程转换为用户线程的程序片段:

    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

注意到,这里不仅会将守护线程转变为用户线程,而且会把优先级转变为Thread.NORM_PRIORITY。

 

如下所示,将守护线程采用线程池的方式开启:

 

package com.daemon;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class DaemonThreadTest
{
	public static void main(String[] args)
	{
		Thread mainThread = new Thread(new Runnable(){
			@Override
			public void run()
			{
				ExecutorService exec = Executors.newCachedThreadPool();
				Thread childThread = new Thread(new ClildThread());
				childThread.setDaemon(true);
				exec.execute(childThread);
				exec.shutdown();
				System.out.println("I'm main thread...");
			}
		});
		mainThread.start();
	}
}

class ClildThread implements Runnable
{
	@Override
	public void run()
	{
		while(true)
		{
			System.out.println("I'm child thread..");
			try
			{
				TimeUnit.MILLISECONDS.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

运行结果:

 

 

I'm main thread...
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..
I'm child thread..(无限输出)

上面代码证实了线程池会将守护线程转变为用户线程。

 

至于为什么jdk会这么做,博主还没有参悟的透彻,如果你了解,希望能留言告知。
 

欢迎跳转到本文的原文链接:https://honeypps.com/java/java-daemon-thread/

欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。

已标记关键词 清除标记
相关推荐
程序员的必经之路! 【限时优惠】 现在下单,还享四重好礼: 1、教学课件免费下载 2、课程案例代码免费下载 3、专属VIP学员群免费答疑 4、下单还送800元编程大礼包 【超实用课程内容】  根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!   套餐中一共包含2门MySQL数据库必学的核心课程(共98课时)   课程1:《MySQL数据库从入门到实战应用》   课程2:《高性能MySQL实战课》   【哪些人适合学习这门课程?】  1)平时只接触了语言基础,并未学习任何数据库知识的人;  2)对MySQL掌握程度薄弱的人,课程可以让你更好发挥MySQL最佳性能; 3)想修炼更好的MySQL内功,工作中遇到高并发场景可以游刃有余; 4)被面试官打破沙锅问到底的问题问到怀疑人生的应聘者。 【课程主要讲哪些内容?】 课程一:《MySQL数据库从入门到实战应用》 主要从基础篇,SQL语言篇、MySQL进阶篇三个角度展开讲解,帮助大家更加高效的管理MySQL数据库。 课程二:《高性能MySQL实战课》主要从高可用篇、MySQL8.0新特性篇,性能优化篇,面试篇四个角度展开讲解,帮助大家发挥MySQL的最佳性能的优化方法,掌握如何处理海量业务数据和高并发请求 【你能收获到什么?】  1.基础再提高,针对MySQL核心知识点学透,用对; 2.能力再提高,日常工作中的代码换新貌,不怕问题; 3.面试再加分,巴不得面试官打破沙锅问到底,竞争力MAX。 【课程如何观看?】  1、登录CSDN学院 APP 在我的课程中进行学习; 2、移动端:CSDN 学院APP(注意不是CSDN APP哦)  本课程为录播课,课程永久有效观看时长 【资料开放】 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化。  下载方式:电脑登录课程观看页面,点击右侧课件,可进行课程资料的打包下载。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页