在 Java 语言中,线程分为两类:用户线程和守护线程,默认状况下咱们创立的线程或线程池都是用户线程,所以用户线程也被称之为一般线程。

想要查看线程到底是用户线程还是守护线程,能够通过 Thread.isDaemon() 办法来判断,如果返回的后果是 true 则为守护线程,反之则为用户线程。

咱们来测试一下默认状况下线程和线程池属于哪种线程类型?测试代码如下:

import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/** * 线程类型:守护线程 OR 用户线程 */public class ThreadType {    public static void main(String[] args) {        // 创立线程        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                //...            }        });        // 创立线程池        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10,                0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));        threadPool.submit(new Runnable() {            @Override            public void run() {                System.out.println("ThreadPool 线程类型:" +                        (Thread.currentThread().isDaemon() == true ? "守护线程" : "用户线程"));            }        });        System.out.println("Thread 线程类型:" +                (thread.isDaemon() == true ? "守护线程" : "用户线程"));        System.out.println("main 线程类型:" +                (Thread.currentThread().isDaemon() == true ? "守护线程" : "用户线程"));    }}

以上程序的执行后果如下图所示:

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

守护线程定义

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

创立守护线程

咱们能够通过 Thread.setDaemon(true) 办法将线程设置为守护线程,比方以下代码的实现:

public static void main(String[] args) {    Thread thread = new Thread(new Runnable() {        @Override        public void run() {            //...        }    });    // 设置线程为守护线程    thread.setDaemon(true);    System.out.println("Thread 线程类型:" +                       (thread.isDaemon() == true ? "守护线程" : "用户线程"));    System.out.println("main 线程类型:" +                       (Thread.currentThread().isDaemon() == true ? "守护线程" : "用户线程"));}

以上程序的执行后果如下图所示:

将线程池设置为守护线程

要把线程池设置为守护线程相对来说麻烦一些,须要将线程池中的所有线程都设置成守护线程,这个时候就须要应用线程工厂 ThreadFactory 来设置了(线程池中的所有线程都是通过线程工厂创立的),它的具体实现代码如下:

public static void main(String[] args) throws InterruptedException {    // 线程工厂(设置守护线程)    ThreadFactory threadFactory = new ThreadFactory() {        @Override        public Thread newThread(Runnable r) {            Thread thread = new Thread(r);            // 设置为守护线程            thread.setDaemon(true);            return thread;        }    };    // 创立线程池    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10,                                                           0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), threadFactory);    threadPool.submit(new Runnable() {        @Override        public void run() {            System.out.println("ThreadPool 线程类型:" +                               (Thread.currentThread().isDaemon() == true ? "守护线程" : "用户线程"));        }    });    Thread.sleep(2000);}

以上程序的执行后果如下图所示:

守护线程 VS 用户线程

通过后面的内容咱们理解了什么是用户线程和守护线程了,那二者有什么区别呢?接下来咱们用一个小示例来察看一下。
接下来咱们将创立一个线程,别离将这个线程设置为用户线程和守护线程,在每个线程中执行一个 for 循环,总共执行 10 次信息打印,每次打印之后休眠 100 毫秒,来察看程序的运行后果。

用户线程

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

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 次打印之后才会失常完结过程。

守护线程

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 次之后再进行敞开,而是当主线程完结之后,守护线程一次循环都没执行就完结了,由此能够看出守护线程和用户线程的不同。

守护线程注意事项

守护线程的设置 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() 之后,岂但程序的执行会报错,而且设置的守护线程也不会失效。

总结

在 Java 语言中线程分为两类:用户线程和守护线程,默认状况下咱们创立的线程或线程池都是用户线程,守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行实现之后程序就会完结运行,程序完结运行时不会管守护线程是否正在运行,由此咱们能够看出守护线程在 Java 体系中权重是比拟低的,这就是守护线程和用户线程的区别。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview