首頁  >  文章  >  Java  >  Java中使用者線程與守護線程如何使用

Java中使用者線程與守護線程如何使用

WBOY
WBOY轉載
2023-05-13 11:28:05993瀏覽

    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());
    }

    以上程式的執行結果為: 

    Java中使用者線程與守護線程如何使用

     從上述結果可以看出,預設主執行緒和建立的新執行緒都為使用者執行緒

    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());
     }

    以上程式的執行結果為: 

    Java中使用者線程與守護線程如何使用

    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;
        }
    });

    #如下圖所示: 

    Java中使用者線程與守護線程如何使用

    如上圖所示,可以看出,整個程式中有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();
        }
    }

    以上程式執行結果如下: 

    Java中使用者線程與守護線程如何使用

     從上述結果可以看出,當程式執行完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();
        }
    }

    #以上程式執行結果如下: 

    Java中使用者線程與守護線程如何使用

     從上述結果可以看出,當線程設定為守護線程之後,整個程式不會等守護線程 for 循環10 次之後再進行關閉,而是當主線程結束之後,守護線程只執行了一次循環就結束運行了,由此可以看出守護線程和使用者線程的不同。

    3.3 小结

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

    4.守护线程注意事项

    守护线程的使用需要注意以下三个问题:

    • 守护线程的设置 setDaemon(true) 必须要放在线程的 start() 之前,否则程序会报错。

    • 在守护线程中创建的所有子线程都是守护线程。

    • 使用 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);
    }

    以上程序执行结果如下: 

    Java中使用者線程與守護線程如何使用

     从上述结果可以看出,当我们将 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);
    }

    以上程序执行结果如下: 

    Java中使用者線程與守護線程如何使用

     从上述结果可以看出,守护线程中创建的子线程,默认情况下也属于守护线程

    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());
    }

    以上程序执行结果如下: 

    Java中使用者線程與守護線程如何使用

     通过上述结果我们可以看出,即使是守护线程,当程序中调用 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();
        }
    }

    以上程序执行结果如下: 

    Java中使用者線程與守護線程如何使用

     通过上述结果可以看出,线程的类型不管是守护线程还是用户线程对程序执行的优先级是没有任何影响的,而当我们将 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();
        }
    }

    以上程序执行结果如下: 

    Java中使用者線程與守護線程如何使用

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

    以上是Java中使用者線程與守護線程如何使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除