Java多线程:守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 

Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。

垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

守护线程一些特性:

1、优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。

2、Thread类与守护线程相关方法

isDaemon() : 测试一个线程是否为守护线程

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

1)、thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程

2)、 在Daemon线程中产生的新线程也是Daemon的。

3)、不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了。如果还有一个或以上的非守护线程则JVM不会退出。

3、守护线程会随时中断,因此不要在守护线程上使用需要释放资源的资源,如输入输出流,数据库连接等所有的守护线程都是后台线程,如果虚拟机中只剩下守护线程,虚拟机就会退出,如下面这个例子:

 1 //完成文件输出的守护线程任务  
 2  public class TestDemo2{     
 3     public static void main(String[] args) throws InterruptedException     
 4     {         
 5         Thread thread= new Thread(){
 6             public void run(){     
 7                 try{     
 8                     Thread.sleep(1000);//守护线程阻塞1秒后运行     
 9                     File f=new File("c:/daemon.txt");     
10                     FileOutputStream os=new FileOutputStream(f,true);     
11                     os.write("daemon".getBytes());     
12                 }     
13                 catch(IOException e1){     
14                     e1.printStackTrace();     
15                 }     
16                 catch(InterruptedException e2){     
17                     e2.printStackTrace();     
18                 } 
19             };     
20             thread.setDaemon(true); //设置守护线程     
21             thread.start(); //开始执行分进程     
22         }     
23     }     
24     //运行结果:文件daemon.txt中没有"daemon"字符串。  

把输入输出逻辑包装进守护线程会使得字符串并没有写入指定文件。原因也很简单,直到主线程完成,守护线程仍处于1秒的阻塞状态。这个时候主线程很快就运行完了,虚拟机退出,Daemon停止服务,输出操作自然失败了。

 1 // Demo.java
 2 class MyThread extends Thread{  
 3     public MyThread() {
 4         super("MyThread");
 5     }
 6 
 7     public void run(){
 8         try {
 9             for (int i=0; i<5; i++) {
10                 Thread.sleep(300);
11                 System.out.println(this.getName() +"(isDaemon="+this.isDaemon()+ ")" +", loop "+i);
12             }
13         } catch (InterruptedException e) {
14         }
15     } 
16 }; 
17 
18 class MyDaemon extends Thread{  
19     public MyDaemon() {
20         super("MyDaemon");
21     }
22 
23     public void run(){
24         try {
25             for (int i=0; i<10000; i++) {
26                 Thread.sleep(100);
27                 System.out.println(this.getName() +"(isDaemon="+this.isDaemon()+ ")" +", loop "+i);
28             }
29         } catch (InterruptedException e) {
30         }
31     } 
32 }
33 public class Demo {  
34     public static void main(String[] args) {  
35 
36         System.out.println(Thread.currentThread().getName()
37                 +"(isDaemon="+Thread.currentThread().isDaemon()+ ")");
38 
39         Thread t1 = new MyThread();    // 新建t1
40         Thread t2 = new MyDaemon();    // 新建t2
41         t2.setDaemon(true);            // 设置t2为守护线程
42         t1.start();                    // 启动t1
43         t2.start();                    // 启动t2
44     }  
45 }
main(isDaemon=false)
MyDaemon(isDaemon=true), loop 0
MyDaemon(isDaemon=true), loop 1
MyThread(isDaemon=false), loop 0
MyDaemon(isDaemon=true), loop 2
MyDaemon(isDaemon=true), loop 3
MyDaemon(isDaemon=true), loop 4
MyThread(isDaemon=false), loop 1
MyDaemon(isDaemon=true), loop 5
MyDaemon(isDaemon=true), loop 6
MyDaemon(isDaemon=true), loop 7
MyThread(isDaemon=false), loop 2
MyDaemon(isDaemon=true), loop 8
MyDaemon(isDaemon=true), loop 9
MyDaemon(isDaemon=true), loop 10
MyThread(isDaemon=false), loop 3
MyDaemon(isDaemon=true), loop 11
MyDaemon(isDaemon=true), loop 12
MyDaemon(isDaemon=true), loop 13
MyThread(isDaemon=false), loop 4

结果说明
(01) 主线程main是用户线程,它创建的子线程t1也是用户线程。
(02) t2是守护线程。在“主线程main”和“子线程t1”(它们都是用户线程)执行完毕,只剩t2这个守护线程的时候,JVM自动退出。

Web应用程序中调度器的启动和关闭问题

我们知道静态变量是ClassLoader级别的,如果Web应用程序停止,这些静态变量也会从JVM中清除。但是线程则是JVM级别的,如果你在Web 应用中启动一个线程,这个线程的生命周期并不会和Web应用程序保持同步。也就是说,即使你停止了Web应用,这个线程依旧是活跃的。正是因为这个很隐晦 的问题,所以很多有经验的开发者不太赞成在Web应用中私自启动线程。 

如果我们手工使用JDK Timer(Quartz的Scheduler),在Web容器启动时启动Timer,当Web容器关闭时,除非你手工关闭这个Timer,否则Timer中的任务还会继续运行! 

Spring 为 JDK Timer 和 Quartz Scheduler 所提供的TimerFactoryBean和SchedulerFactoryBean能够和Spring容器的生命周期关联,在 Spring容器启动时启动调度器,而在Spring容器关闭时,停止调度器。所以在Spring中通过这两个FactoryBean配置调度器,再从 Spring IoC中获取调度器引用进行任务调度将不会出现这种Web容器关闭而任务依然运行的问题。而如果你在程序中直接使用Timer或Scheduler,如不 进行额外的处理,将会出现这一问题。 

 

其实定时任务都是开启的线程,如果能把开启的线程都设置成守护线程,那么也行。

参考资料:

http://www.cnblogs.com/skywang12345/p/3479982.html

http://blog.csdn.net/shimiso/article/details/8964414

http://langgufu.iteye.com/blog/2184647

原文地址:https://www.cnblogs.com/2015110615L/p/6747241.html