在 Java 语言中线程分为两类:用户线程和守护线程,而二者之间的区别却鲜有人知,所以本文磊哥带你来看二者之间的区别,以及守护线程须要留神的一些事项。

1.默认用户线程

Java 语言中无论是线程还是线程池,默认都是用户线程,因而用户线程也被成为一般线程。

以线程为例,想要查看线程是否为守护线程只需通过调用 isDaemon() 办法查问即可,如果查问的值为 false 则示意不为守护线程,天然也就属于用户线程了,如下代码所示:

public static void main(String[] args) throws InterruptedException {    Thread thread = new Thread(new Runnable() {        @Override        public void run() {            System.out.println("我是子线程");        }    });    System.out.println("子线程==守护线程:" + thread.isDaemon());    System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());}

以上程序的执行后果为:

从上述后果能够看出,默认状况下主线程和创立的新线程都为用户线程

PS:Thread.currentThread() 的意思是获取执行以后代码的线程实例。

2.被动批改为守护线程

守护线程(Daemon Thread)也被称之为后盾线程或服务线程,守护线程是为用户线程服务的,当程序中的用户线程全副执行完结之后,守护线程也会追随完结。

守护线程的角色就像“服务员”,而用户线程的角色就像“顾客”,当“顾客”全副走了之后(全副执行完结),那“服务员”(守护线程)也就没有了存在的意义,所以当一个程序中的全副用户线程都完结执行之后,那么无论守护线程是否还在工作都会随着用户线程一块完结,整个程序也会随之完结运行。

那如何将默认的用户线程批改为守护线程呢?

这个问题要分为两种状况来答复,首先如果是线程,则能够通过设置 setDaemon(true) 办法将用户线程间接批改为守护线程,而如果是线程池则须要通过 ThreadFactory 将线程池中的每个线程都为守护线程才行,接下来咱们别离来实现一下。

2.1 设置线程为守护线程

如果应用的是线程,能够通过 setDaemon(true) 办法将线程类型更改为守护线程,如下代码所示:

 public static void main(String[] args) throws InterruptedException {     Thread thread = new Thread(new Runnable() {         @Override         public void run() {             System.out.println("我是子线程");         }     });     // 设置子线程为守护线程     thread.setDaemon(true);     System.out.println("子线程==守护线程:" + thread.isDaemon());     System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon()); }

以上程序的执行后果为:

2.2 设置线程池为守护线程

要把线程池设置为守护线程相对来说麻烦一些,须要将线程池中的所有线程都设置成守护线程,这个时候就须要应用 ThreadFactory 来定义线程池中每个线程的线程类型了,具体实现代码如下:

// 创立固定个数的线程池ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {    @Override    public Thread newThread(Runnable r) {        Thread t = new Thread(r);        // 设置线程为守护线程        t.setDaemon(false);        return t;    }});

如下图所示:

如上图所示,能够看出,整个程序中有 10 个守护线程都是我创立的。其余几种创立线程池的设置形式相似,都是通过 ThreadFactory 对立设置的,这里就不一一列举了。

3.守护线程 VS 用户线程

通过后面的学习咱们能够创立两种不同的线程类型了,那二者有什么差别呢?接下来咱们应用一个小示例来看一下。

上面咱们创立一个线程,别离将这个线程设置为用户线程和守护线程,在每个线程中执行一个 for 循环,总共执行 10 次信息打印,每次打印之后休眠 100 毫秒,来察看程序的运行后果。

3.1 用户线程

新建的线程默认就是用户线程,因而咱们无需对线程进行任何非凡的解决,执行 for 循环即可(总共执行 10 次信息打印,每次打印之后休眠 100 毫秒),实现代码如下:

/** * Author:Java中文社群 */public class DaemonExample {    public static void main(String[] args) throws InterruptedException {        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                for (int i = 1; i <= 10; i++) {                    // 打印 i 信息                    System.out.println("i:" + i);                    try {                        // 休眠 100 毫秒                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        });        // 启动线程        thread.start();    }}

以上程序执行后果如下:

从上述后果能够看出,当程序执行完 10 次打印之后才会失常完结过程。

3.2 守护线程

/** * Author:Java中文社群 */public class DaemonExample {    public static void main(String[] args) throws InterruptedException {        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                for (int i = 1; i <= 10; i++) {                    // 打印 i 信息                    System.out.println("i:" + i);                    try {                        // 休眠 100 毫秒                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        });        // 设置为守护线程        thread.setDaemon(true);        // 启动线程        thread.start();    }}

以上程序执行后果如下:

从上述后果能够看出,当线程设置为守护线程之后,整个程序不会等守护线程 for 循环 10 次之后再进行敞开,而是当主线程完结之后,守护线程只执行了一次循环就完结运行了,由此能够看出守护线程和用户线程的不同。

3.3 小结

守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行实现之后程序就会完结运行,程序完结运行时不会管守护线程是否正在运行,由此咱们能够看出守护线程在 Java 体系中权重是比拟低的。

4.守护线程注意事项

守护线程的应用须要留神以下三个问题:

  1. 守护线程的设置 setDaemon(true) 必须要放在线程的 start() 之前,否则程序会报错。
  2. 在守护线程中创立的所有子线程都是守护线程。
  3. 应用 jojn() 办法会期待一个线程执行完,无论此线程是用户线程还是守护线程。

接下来咱们别离演示一下,以上的注意事项。

4.1 setDaemon 执行程序

当咱们将 setDaemon(true) 设置在 start() 之后,如下代码所示:

public static void main(String[] args) throws InterruptedException {    Thread thread = new Thread(new Runnable() {        @Override        public void run() {            for (int i = 1; i <= 10; i++) {                // 打印 i 信息                System.out.println("i:" + i + ",isDaemon:" +                            Thread.currentThread().isDaemon());                try {                    // 休眠 100 毫秒                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    });    // 启动线程    thread.start();    // 设置为守护线程    thread.setDaemon(true);}

以上程序执行后果如下:

从上述后果能够看出,当咱们将 setDaemon(true) 设置在 start() 之后,岂但程序的执行会报错,而且设置的守护线程也不会失效。

4.2 守护线程的子线程

public static void main(String[] args) throws InterruptedException {    Thread thread = new Thread(new Runnable() {        @Override        public void run() {            Thread thread2 = new Thread(new Runnable() {                @Override                public void run() {                }            });            System.out.println("守护线程的子线程 thread2 isDaemon:" +                               thread2.isDaemon());        }    });    // 设置为守护线程    thread.setDaemon(true);    // 启动线程    thread.start();    Thread.sleep(1000);}

以上程序执行后果如下:

从上述后果能够看出,守护线程中创立的子线程,默认状况下也属于守护线程

4.3 join 与守护线程

通过 3.2 局部的内容咱们能够看出,默认状况下程序完结并不会期待守护线程执行完,而当咱们调用线程的期待办法 join() 时,执行的后果就会和 3.2 的后果有所不同,上面咱们一起来看吧,示例代码如下:

public static void main(String[] args) throws InterruptedException {    Thread thread = new Thread(new Runnable() {        @Override        public void run() {            for (int i = 1; i <= 10; i++) {                // 打印 i 信息                System.out.println("i:" + i);                try {                    // 休眠 100 毫秒                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    });    // 设置为守护线程    thread.setDaemon(true);    // 启动线程    thread.start();    // 期待线程执行完    thread.join();    System.out.println("子线程==守护线程:" + thread.isDaemon());    System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());}

以上程序执行后果如下:

通过上述后果咱们能够看出,即便是守护线程,当程序中调用 join() 办法时,程序仍然会期待守护线程执行实现之后再完结过程。

5.守护线程利用场景

守护线程的典型利用场景就是垃圾回收线程,当然还有一些场景也非常适合应用守护线程,比方服务器端的衰弱检测性能,对于一个服务器来说衰弱检测性能属于非核心非主流的服务业务,像这种为了次要业务服务的业务性能就十分适合应用守护线程,当程序中的次要业务都执行实现之后,服务业务也会跟随者一起销毁。

6.守护线程的执行优先级

首先来说,线程的类型(用户线程或守护线程)并不影响线程执行的优先级,如下代码所示,定义一个用户线程和守护线程,别离执行 10 万次循环,通过观察最初的打印后果来确认线程类型对程序执行优先级的影响。

public class DaemonExample {    private static final int count = 100000;    public static void main(String[] args) throws InterruptedException {        // 定义工作        Runnable runnable = new Runnable() {            @Override            public void run() {                for (int i = 0; i < count; i++) {                    System.out.println("执行线程:" + Thread.currentThread().getName());                }            }        };        // 创立守护线程 t1        Thread t1 = new Thread(runnable, "t1");        // 设置为守护线程        t1.setDaemon(true);        // 启动线程        t1.start();        // 创立用户线程 t2        Thread t2 = new Thread(runnable, "t2");        // 启动线程        t2.start();    }}

以上程序执行后果如下:

通过上述后果能够看出,线程的类型不论是守护线程还是用户线程对程序执行的优先级是没有任何影响的,而当咱们将 t2 的优先级调整为最大时,整个程序的运行后果就齐全不同了,如下代码所示:

public class DaemonExample {    private static final int count = 100000;    public static void main(String[] args) throws InterruptedException {        // 定义工作        Runnable runnable = new Runnable() {            @Override            public void run() {                for (int i = 0; i < count; i++) {                    System.out.println("执行线程:" + Thread.currentThread().getName());                }            }        };        // 创立守护线程 t1        Thread t1 = new Thread(runnable, "t1");        // 设置为守护线程        t1.setDaemon(true);        // 启动线程        t1.start();        // 创立用户线程 t2        Thread t2 = new Thread(runnable, "t2");        // 设置 t2 的优先级为最高        t2.setPriority(Thread.MAX_PRIORITY);        // 启动线程        t2.start();    }}

以上程序执行后果如下:

通过上述的后果能够看出,程序的类型和程序执行的优先级是没有任何关系,当新创建的线程默认的优先级都是 5 时,无论是守护线程还是用户线程,它们执行的优先级都是雷同的,当将二者的优先级设置不同时,执行的后果也会随之扭转(优先级设置的越高,最早被执行的概率也越大)。

7.总结

在 Java 语言中线程分为用户线程和守护线程,守护线程是用来为用户线程服务的,当一个程序中的所有用户线程都完结之后,无论守护线程是否在工作都会追随用户线程一起完结。守护线程从业务逻辑层面来看权重比拟低,但对于线程调度器来说无论是守护线程还是用户线程,在优先级雷同的状况下被执行的概率都是雷同的。守护线程的经典应用场景是垃圾回收线程,守护线程中创立的线程默认状况下也都是守护线程。

关注公号「Java中文社群」查看更多有意思、涨常识的并发编程文章。