当前位置: 首页 > 科技观察

Java中的用户线程和守护线程有这么大的区别吗?

时间:2023-03-13 05:19:49 科技观察

本文转载自微信公众号“Java中文社区”,作者雷哥。转载本文请联系Java中文社区公众号。在Java语言中,线程分为用户线程和守护线程两大类,而两者的区别鲜为人知,那么这篇雷哥就带大家看看两者的区别,以及守护线程的一些要点线程需要注意的事项。1、默认用户线程无论是Java语言中的线程还是线程池,默认都是用户线程,所以用户线程也称为普通线程。以线程为例,如果要查看一个线程是否是守护线程,只需要调用isDaemon()方法查询即可。如果查询值为false,说明不是守护线程,自然属于用户线程,如下代码所示:{@Overridepublicvoidrun(){System.out.println("我是子线程");}});System.out.println("子线程==守护线程:"+thread.isDaemon());System.out.println("Mainthread==daemonthread:"+Thread.currentThread().isDaemon());}上面程序的执行结果为:从上面的结果可以看出,默认情况下,主线程和创建的新线程都是用户线程。PS:Thread.currentThread()的意思是获取执行当前代码的线程实例。2、主动修改为守护线程DaemonThread也称为后台线程或服务线程。守护线程为用户线程服务。当程序中的所有用户线程都执行完毕后,守护线程也随之结束。.守护线程的角色就像是“服务器”,用户线程的角色就像是“顾客”。当所有“客户”都走了(所有执行结束)时,“服务器”(守护线程)就没有存在的意义了,所以当一个程序中的所有用户线程都执行完后,不管守护线程是否还在工作,它会和用户线程一起结束,整个程序也会结束运行。那么如何将默认用户线程修改为守护线程呢?这个问题要分两种情况来回答,首先如果是线程,可以通过设置setDaemon(true)方法直接修改用户线程为守护线程,如果是线程池则需要使用ThreadFactory来使线程池中的每个线程成为守护线程。接下来我们单独实现。2.1设置线程为守护线程如果使用的是线程,可以通过setDaemon(true)方法将线程类型改为守护线程,如下代码所示:publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("我是子线程");}});//设置子线程为守护线程thread.setDaemon(true);System.out.println("子线程==守护线程:"+thread.isDaemon());System.out.println("Mainthread==daemonthread:"+Thread.currentThread().isDaemon());}上面程序的执行结果为:2.2设置线程池为daemon相对麻烦thread将线程池设置为守护线程。您需要将线程池中的所有线程设置为守护线程。这时候就需要使用ThreadFactory来定义线程池中每个线程的线程类型。具体实现代码如下://创建固定数量的线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(10,newThreadFactory(){@OverridepublicThreadnewThread(Runnabler){Threadt=newThread(r);//设置线程为守护进程threadt.setDaemon(false);return;}});如下图所示:如上图所示,可以看出我创建的整个程序中有10个守护线程。创建其他线程池的设置方法类似,都是通过ThreadFactory统一设置的,这里就不一一列举了。3.守护线程VS用户线程通过前面的学习,我们可以创建两种不同的线程类型。两者有什么区别?接下来,我们来看一个小例子。接下来我们创建一个线程,将这个线程分别设置为用户线程和守护线程,在每个线程中执行一个for循环,一共执行10次信息打印,每次打印后休眠100毫秒,观察运行结果该程序。3.1用户线程新建的线程默认是用户线程,所以我们不需要对线程做任何特殊处理,执行for循环即可(一共打印10次信息,之后休眠100毫秒)每次打印),实现代码如下:/***作者:Java中文社区*/publicclassDaemonExample{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=1;i<=10;i++){//打印i信息System.out.println("i:"+i);try{//休眠100毫秒Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}}});//启动线程thread.start();}}上面程序的执行结果如下:从上面的结果可以看出进程会结束通常在程序完成打印10次后。3.2DaemonThread/***作者:Java中文社区*/publicclassDaemonExample{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=1;i<=10;i++){//打印i信息System.out.println("i:"+i);try{//休眠100毫秒Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}}});//设置为守护线程thread.setDaemon(true);//启动线程thread.start();}}以上程序执行结果如下:从以上结果可以看出当线程设置为守护线程时,守护线程for循环10次后整个程序不会关闭,但是当主线程结束时,守护线程只执行一次循环就结束运行。从这里我们可以看出守护线程和用户线程的区别。3.3总结守护线程为用户线程服务。当一个程序中的所有用户线程都执行完后,该程序就会结束运行。当程序结束时,并不关心守护线程是否在运行。由此我们可以看出守护线程在Java系统中的权重是比较低的。4、守护线程注意事项使用守护线程需要注意以下三个问题:守护线程设置setDaemon(true)必须放在线程的start()之前,否则程序会报错。在守护线程中创建的所有子线程都是守护线程。使用jojn()方法会等待一个线程完成,不管这个线程是用户线程还是守护线程。下面我们就以上注意事项分别进行演示。4.1setDaemon的执行顺序当我们在start()之后设置setDaemon(true)时,如下代码所示:1;i<=10;i++){//打印i信息System.out.println("i:"+i+",isDaemon:"+Thread.currentThread().isDaemon());try{//休眠for100毫秒Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}}});//启动线程thread.start();//设置为守护线程thread.setDaemon(true);上面的程序执行结果如下:从上面的结果我们可以看出,当我们在start()之后设置setDaemon(true)时,不仅程序执行会报错,而且设置的守护线程也不会占用影响。4.2守护线程的子线程out.println("守护线程的子线程thread2isDaemon:"+thread2.isDaemon());}});//设为守护线程thread.setDaemon(true);//启动线程thread.start();Thread.sleep(1000);}上面程序的执行结果如下:从上面的结果可以看出,守护线程中创建的子线程默认也是属于守护线程的。4.3join和守护线程通过3.2部分的内容我们可以看到,默认情况下,程序结束不会等待守护线程的执行,但是当我们调用线程的等待方法join()时,执行结果会和3.2的结果不一样,一起来看看吧,示例代码如下:{for(inti=1;i<=10;i++){//打印i信息System.out.println("i:"+i);try{//休眠100毫秒Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}}}});//设置为守护线程thread.setDaemon(true);//启动线程thread.start();//等待线程结束executingthread.join();System.out.println("子线程==守护线程:"+thread.isDaemon());System.out.println("Mainthread==daemonthread:"+Thread.currentThread().isDaemon());}以上程序执行结果如下:通过以上结果,我们可以看出,即使如果是守护线程,当程序中调用join()方法时,程序仍然会等待守护线程执行完毕,才会结束进程。5、守护线程应用场景守护线程的典型应用场景是垃圾回收线程。当然也有一些场景也非常适合使用守护线程,比如服务端的健康检测功能。对于服务器来说,健康检测功能是非核心非主流的。服务类业务,比如这类为主业务服务的业务功能,非常适合使用守护线程。当程序中的主业务执行完毕后,服务业务会连同followers一起销毁。6、守护线程的执行优先级首先,线程的类型(用户线程还是守护线程)并不影响线程执行的优先级。如下代码所示,分别定义一个用户线程和一个守护线程执行10万次循环,通过观察最终的打印结果来确认线程类型对程序执行优先级的影响。publicclassDaemonExample{privatestaticfinalintcount=100000;publicstaticvoidmain(String[]args)throwsInterruptedException{//定义任务Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){for(inti=0;i