守护线程

守护线程

线程分类

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

定义

守护线程:守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。

优先级

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

设置

通过设置`setDaemon(true)`来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在线程创建对象之前,用线程对象的`setDaemon`方法。**example:**垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的**Thread**,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当JVM上没有用户线程时。垃圾回收线程会自动离开。它使用在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

设置的方法如下:
1
2
3
4
5
Thread daemonThread  = new Thread();
//设定 daemonthread 为守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
//验证当前线程是否为守护线程,返回true则为守护线程
daemonThread.isDaemon();

生命周期

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

用个比较通俗的比喻,任何一个守护线程都是整个JVM中所有非守护线程的保护:==只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作==;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。**Daemon**的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),它就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如何User Thread 已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了.因为没有了被守护者,Daemon也就没有工作可做了也就没有继续运行程序的必要了。

守护线程的注意点

  1. thread.setDaemon(true)必须在thread.start()之前设置,否则会报一个IllegalThreadStateException异常。你不能把一个正在运行的常规线程设置为守护线程。
  2. Daemon线程中产生的新线程也是Daemon的。
  3. 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。(不能保证,当用户进程都退出了,守护进程的读写任务是否完成,及时没有完成,守护进程也会自动退出)
  4. 写Java多线程程序时,一般比较喜欢用java自带的多线程框架,比如ExecutorService,但是Java的线程池会将守护线程转换为用户线程,所以如果使用守护线程,就不能用Java的线程池。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//完成文件输出的守护线程任务  
import java.io.*;

class TestRunnable implements Runnable{
public void run(){
try{
Thread.sleep(1000);//守护线程阻塞1秒后运行
File f=new File("daemon.txt");
FileOutputStream os=new FileOutputStream(f,true);
os.write("daemon".getBytes());
}
catch(IOException e1){
e1.printStackTrace();
}
catch(InterruptedException e2){
e2.printStackTrace();
}
}
}
public class TestDemo2{
public static void main(String[] args) throws InterruptedException
{
Runnable tr=new TestRunnable();
Thread thread=new Thread(tr);
thread.setDaemon(true); //设置守护线程
thread.start(); //开始执行分进程
}
}
//运行结果:文件daemon.txt中没有"daemon"字符串。
原因很简单,知道主线程完成,守护线程仍处于1秒的阻塞状态。这个时候主线程很快就运行完了,虚拟机退出,**Daemon**停止服务,输出操作自然失败了。

线程池中将daemon线程转换为用户线程的程序片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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**。

如下所示,将守护线程采用线程池的方式开启:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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();
}
}
}
}
运行结果:
1
2
3
4
5
6
7
8
9
10
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..(无限输出)

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

0%