成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

來聊聊守護(hù)線程和 JVM 的優(yōu)雅關(guān)閉

開發(fā)
本文我們的虛擬機(jī)鉤子、守護(hù)線程、finalize三個(gè)角度針對(duì)Java程序優(yōu)雅關(guān)閉的哲學(xué)進(jìn)行一些實(shí)踐演示和建議,希望對(duì)你有幫助。

本文原本是針對(duì)守護(hù)線程的一些探討,感覺知識(shí)點(diǎn)稍顯淺薄,故基于原有文章進(jìn)行迭代補(bǔ)充對(duì)于Java程序優(yōu)雅關(guān)閉的一些思考。

一、JVM中的關(guān)閉

1. 詳解虛擬機(jī)鉤子

在Java進(jìn)程開發(fā)中,對(duì)于重量級(jí)的系統(tǒng)資源關(guān)閉或者進(jìn)程資源整理或信號(hào)輸出,常常會(huì)通過Java內(nèi)置的addShutdownHook方法注冊(cè)回調(diào)函數(shù),確保在Java進(jìn)程關(guān)閉不再使用這些資源時(shí)將其釋放,例如hutool這個(gè)工具類對(duì)應(yīng)連接池的管理工具GlobalDSFactory,其底層就會(huì)在類加載初始化時(shí)利用addShutdownHook注冊(cè)一個(gè)連接池銷毀的回調(diào)函數(shù):

/*
  * 設(shè)置在JVM關(guān)閉時(shí)關(guān)閉所有數(shù)據(jù)庫(kù)連接
  */
 static {
  // JVM關(guān)閉時(shí)關(guān)閉所有連接池
  Runtime.getRuntime().addShutdownHook(new Thread() {
   @Override
   public void run() {
    if (null != factory) {
     factory.destroy();
     StaticLog.debug("DataSource: [{}] destroyed.", factory.dataSourceName);
     factory = null;
    }
   }
  });
 }

而虛擬機(jī)鉤子注冊(cè)的原理本質(zhì)上就是在調(diào)用addShutdownHook時(shí),其底層將這個(gè)現(xiàn)場(chǎng)hook注冊(cè)到一個(gè)hooks的map容器中,并在shutdown的時(shí)候遍歷調(diào)用這些hook線程:

對(duì)應(yīng)的我們也給出addShutdownHook的實(shí)現(xiàn),可以看到其底層就是調(diào)用ApplicationShutdownHooks來注冊(cè)hook:

public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

而步入這個(gè)add方法后可以看到其內(nèi)部本質(zhì)上就是在必要的校驗(yàn)后,存入到hooks這個(gè)map中:

private static IdentityHashMap<Thread, Thread> hooks;

static synchronized void add(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook.isAlive())
            throw new IllegalArgumentException("Hook already running");

        if (hooks.containsKey(hook))
            throw new IllegalArgumentException("Hook previously registered");

        hooks.put(hook, hook);
    }

當(dāng)觸發(fā)虛擬機(jī)鉤子關(guān)閉時(shí),其內(nèi)部就會(huì)針對(duì)hooks進(jìn)行遍歷并按照如下邏輯處理:

  • 將hook線程啟動(dòng),執(zhí)行hook邏輯
  • 調(diào)用join確保該hook能夠準(zhǔn)確執(zhí)行完成
static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }
  //遍歷hook線程啟動(dòng)
        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
                //調(diào)用join加入主線程確保當(dāng)前線程能夠正確執(zhí)行完成
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

當(dāng)所有關(guān)閉鉤子都執(zhí)行結(jié)束時(shí),如果runFinalizersOnExit為true,那么JVM就會(huì)運(yùn)行終結(jié)器finalizers,此時(shí)JVM并不會(huì)停止或者關(guān)閉仍然在運(yùn)行的應(yīng)用線程。直到最終JVM結(jié)束,應(yīng)用線程才會(huì)被關(guān)閉,對(duì)應(yīng)的我們可以在源碼Shutdown的exit方法印證:

static void exit(int status) {
        boolean runMoreFinalizers = false;
        synchronized (lock) {
             //......
            case FINALIZERS:
                if (status != 0) {
                    /* Halt immediately on nonzero status */
                    halt(status);
                } else {
                   //......
                   //將runFinalizersOnExit賦值給runMoreFinalizers 
                    runMoreFinalizers = runFinalizersOnExit;
                }
                break;
            }
        }
        //如果runMoreFinalizers 為true,則運(yùn)行終結(jié)器
        if (runMoreFinalizers) {
            runAllFinalizers();
            halt(status);
        }
        //......
    }

2. 虛擬機(jī)鉤子串行化使用

需要注意的虛擬機(jī)鉤子注冊(cè)后的調(diào)用時(shí)機(jī),當(dāng)JVM執(zhí)行關(guān)閉鉤子的時(shí)候,如果守護(hù)或者非守護(hù)線程也在運(yùn)行,那么虛擬機(jī)鉤子就可能和這些線程并發(fā)的執(zhí)行,即虛擬機(jī)鉤子可能會(huì)并行的執(zhí)行一些工作,所以對(duì)于一些存在依賴性的共享數(shù)據(jù)操作,虛擬機(jī)鉤子要慎重使用。

例如我們用虛擬機(jī)鉤子將日志服務(wù)關(guān)閉,此時(shí)如果另外的虛擬機(jī)鉤子需要使用日志打印,可能就會(huì)報(bào)錯(cuò):

例如我們的日志框架LogService ,本質(zhì)上就是對(duì)于文件流的寫入和關(guān)閉:

static class LogService {

        private static final BufferedWriter writer = FileUtil.getWriter("F:\\tmp\\log.txt", Charset.defaultCharset(), true);

        @SneakyThrows
        public void log(String msg) {//將數(shù)據(jù)寫入日志中
            writer.write(msg);
        }


        public void close() {
            try {
                writer.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

如下圖所說,若在虛擬機(jī)鉤子上注冊(cè)關(guān)閉打印和關(guān)閉日志框架的鉤子,就有可能出現(xiàn)打印鉤子拋出stream close的錯(cuò)誤:

LogService logService = new LogService();



        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            //拋出stream close的錯(cuò)誤
            logService.log("hello world");
        }));

        /**
         * 注冊(cè)虛擬機(jī)鉤子
         */
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            //執(zhí)行一些應(yīng)用程序的資源關(guān)閉
            logService.close();
        }));

總的來說,使用虛擬機(jī)鉤子必須注意:

  • 虛擬機(jī)鉤子要保證線程安全,即針對(duì)共享資源做好同步把控
  • 虛擬機(jī)鉤子盡量串行化執(zhí)行,且鉤子之間不可以有任何依賴
  • 關(guān)閉鉤子應(yīng)該盡快的退出,因?yàn)樗苯拥臎Q定的JVM退出的結(jié)束時(shí)間

二、守護(hù)線程

1. 守護(hù)線程的基本概念

很多人對(duì)守護(hù)線程都不陌生,對(duì)于守護(hù)線程大部分讀者都停留在JDK官方文檔所介紹的概念:

The Java Virtual Machine exits when the only threads running are all daemon threads.

文檔的意思是當(dāng)JVM中不存在任何一個(gè)正在運(yùn)行的非守護(hù)線程時(shí),JVM進(jìn)程會(huì)直接退出。

讀起來很拗口對(duì)不對(duì),沒關(guān)系,本文就會(huì)基于幾個(gè)代碼示例,讓你更深層次的理解守護(hù)線程。在此之前,讀者不妨自測(cè)一下,下面這幾道面試題:

  • 守護(hù)線程和普通線程有什么區(qū)別?
  • 守護(hù)線程默認(rèn)優(yōu)先級(jí)是多少?
  • 若父線程為守護(hù)線程,在其內(nèi)部創(chuàng)建一個(gè)普通線程,父線程停止,子線程是否也會(huì)停止呢?
  • 如何創(chuàng)建守護(hù)線程池?
  • 守護(hù)線程使用有哪些注意事項(xiàng)?

2. 守護(hù)線程和普通線程的區(qū)別

要了解區(qū)別就先來了解一下兩者的使用,非守護(hù)線程,也就我們?nèi)粘?chuàng)建的普通線程,可以看到這段代碼創(chuàng)建了一個(gè)普通線程,在無限循環(huán)的定時(shí)輸出內(nèi)容,而主線程僅僅是輸出一段文字后就不做任何動(dòng)作了。

public static void main(String[] args) {

        Thread t = new Thread(() -> {
            while (true) {
                log.info("普通線程執(zhí)行了......");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();
       log.info("主線程運(yùn)行結(jié)束");


    }

對(duì)應(yīng)的輸出結(jié)果如下,可以看到,即使主線程停止運(yùn)行了,而非守護(hù)線程也仍然會(huì)在運(yùn)行,也就是JDK官方文檔的字面含義,普通線程不停止,JVM就不停止運(yùn)行:

12:44:57.022 [Thread-0] INFO com.sharkChili.webTemplate.Main - 普通線程執(zhí)行了......
12:44:57.022 [main] INFO com.sharkChili.webTemplate.Main - 主線程運(yùn)行結(jié)束
12:45:02.031 [Thread-0] INFO com.sharkChili.webTemplate.Main - 普通線程執(zhí)行了......

基于上述代碼,用setDaemon(true)將該線程設(shè)置為守護(hù)線程:

public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {
                log.info("守護(hù)線程執(zhí)行了......");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //設(shè)置當(dāng)前線程為守護(hù)線程
        t.setDaemon(true);
        t.start();
        log.info("主線程運(yùn)行結(jié)束");
    }

輸出結(jié)果如下,可以看到隨著主線程的消亡,守護(hù)線程也會(huì)隨之停止,不再運(yùn)行,自此我相信讀者可以理解JDK官方文檔所說的那句話了,只要有一個(gè)普通線程在,JVM就不會(huì)退出,只要所有普通線程停止工作,JVM自動(dòng)退出,守護(hù)線程也會(huì)自動(dòng)結(jié)束。

12:44:23.239 [Thread-0] INFO com.sharkChili.webTemplate.Main - 守護(hù)線程執(zhí)行了......
12:44:23.239 [main] INFO com.sharkChili.webTemplate.Main - 主線程運(yùn)行結(jié)束

3. 守護(hù)線程和普通線程優(yōu)先級(jí)的區(qū)別

我們可以通過getPriority方法查看兩者的區(qū)別:

public static void main(String[] args) {

        Thread t = new Thread(() -> {

            log.info("守護(hù)線程優(yōu)先級(jí):{}", Thread.currentThread().getPriority());
        });

        //設(shè)置當(dāng)前線程為守護(hù)線程
        t.setDaemon(true);
        t.start();
        log.info("主線程運(yùn)行結(jié)束,當(dāng)前線程運(yùn)行優(yōu)先級(jí):{}", Thread.currentThread().getPriority());


    }

從輸出結(jié)果來看,兩者的優(yōu)先級(jí)是一樣的,都為5:

12:54:36.344 [main] INFO com.sharkChili.webTemplate.Main - 主線程運(yùn)行結(jié)束,當(dāng)前線程運(yùn)行優(yōu)先級(jí):5
12:54:36.344 [Thread-0] INFO com.sharkChili.webTemplate.Main - 守護(hù)線程優(yōu)先級(jí):5

4. 父守護(hù)線程問題

我們創(chuàng)建了一個(gè)守護(hù)線程,在其runnable實(shí)現(xiàn)中創(chuàng)建一個(gè)子線程:

public static void main(String[] args) {

        Thread parentThread = new Thread(() -> {
            Thread childThread = new Thread(() -> {
                while (true) {
                    log.info("子線程運(yùn)行中,是否為守護(hù)線程:{}",Thread.currentThread().isDaemon());
                    try {
                        TimeUnit.HOURS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            });

            childThread.start();

            log.info("parentThread守護(hù)線程運(yùn)行中");
        });

        //設(shè)置當(dāng)前線程為守護(hù)線程
        parentThread.setDaemon(true);
        parentThread.start();
        log.info("主線程運(yùn)行結(jié)束");


    }

從輸出結(jié)果來看,父線程為守護(hù)線程時(shí),其內(nèi)部創(chuàng)建的子線程也為守護(hù)線程,所以隨著父線程的銷毀,子線程也會(huì)同步銷毀。

00:05:56.869 [Thread-1] INFO com.sharkChili.webTemplate.Main - 子線程運(yùn)行中,是否為守護(hù)線程:true
00:05:56.869 [main] INFO com.sharkChili.webTemplate.Main - 主線程運(yùn)行結(jié)束
00:05:56.869 [Thread-0] INFO com.sharkChili.webTemplate.Main - parentThread守護(hù)線程運(yùn)行中

5. 守護(hù)線程池的創(chuàng)建

public static void main(String[] args) {

        ExecutorService threadPool = Executors.newFixedThreadPool(10, ThreadFactoryBuilder.create()
                .setNamePrefix("worker-")
                .setDaemon(true)
                .build());
        threadPool.execute(()->{
            while (true){
                try {
                    log.info("守護(hù)線程運(yùn)行了");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        log.info("主線程退出");


    }

6. 守護(hù)線程的使用場(chǎng)景

因?yàn)槭刈o(hù)線程擁有自動(dòng)結(jié)束自己生命周期的特性,當(dāng)JVM中沒有一個(gè)普通線程運(yùn)行時(shí),JVM會(huì)退出,即所有守護(hù)線程會(huì)自動(dòng)停止,所以守護(hù)線程的使用場(chǎng)景可以有以下幾種:

  • 垃圾回收線程就是典型的守護(hù)線程,在后臺(tái)進(jìn)行垃圾對(duì)象回收的工作。
  • 非核心業(yè)務(wù)工作可交由守護(hù)線程,例如:各類信息統(tǒng)計(jì)、服務(wù)監(jiān)控等,一旦進(jìn)程結(jié)束運(yùn)行則這些守護(hù)線程停止工作。

7. 守護(hù)線程注意事項(xiàng)

  • 復(fù)雜計(jì)算、資源回收這種不建議使用守護(hù)線程。
  • setDaemon要在start方法前面,否者該設(shè)置會(huì)不生效。

三、finalize關(guān)閉的哲學(xué)

1. 基本介紹

針對(duì)一些系統(tǒng)資源例如文件句柄或者套接字句柄,當(dāng)不需要它們時(shí),垃圾回收器定義了finalize方法進(jìn)行一些資源關(guān)閉,一旦垃圾回收器回收這些對(duì)象之后,對(duì)應(yīng)的資源就會(huì)調(diào)用finalize釋放。

例如FileInputStream的finalize方法,它就會(huì)檢查當(dāng)前文件句柄是否非空,然后顯示的調(diào)用一下close方法:

protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
           //關(guān)閉文件句柄
            close();
        }
    }

2. 終結(jié)器注意事項(xiàng)和正確資源關(guān)閉姿勢(shì)

需要注意的finalize在JVM運(yùn)行中可能會(huì)執(zhí)行也可能不會(huì)執(zhí)行,JVM對(duì)此無法做出保證,所以它運(yùn)行時(shí)存著極端的不確定性,所以進(jìn)行資源關(guān)閉時(shí),我們非常不建議使用finalize。

正確的一些系統(tǒng)資源關(guān)閉回收,筆者更建議是使用階段采用try-with-resource手動(dòng)關(guān)閉資源:

//使用try-with-resource手動(dòng)關(guān)閉資源
try(BufferedReader reader = FileUtil.getUtf8Reader("filePahth")){
            System.out.println(reader.readLine());
        }catch (Exception e){
            //異常處理
        }
責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2021-01-19 10:35:49

JVM場(chǎng)景函數(shù)

2022-04-11 08:17:07

JVMJava進(jìn)程

2021-03-28 09:17:18

JVM場(chǎng)景鉤子函數(shù)

2025-06-11 08:20:00

JVM線程代碼

2024-11-13 16:37:00

Java線程池

2024-10-14 14:28:19

支付系統(tǒng)設(shè)計(jì)

2023-12-20 10:04:45

線程池Java

2024-08-07 08:22:27

2023-09-01 08:59:57

2024-02-04 09:00:00

向量查詢數(shù)據(jù)檢索MyScale

2020-02-24 21:43:36

avaJVM 級(jí)鎖線程安全

2021-05-06 08:11:03

Java用戶線程守護(hù)線程

2024-02-21 08:19:54

2023-10-20 08:00:55

PodRainbow部署

2024-04-24 12:34:08

Spring事務(wù)編程

2023-07-14 12:28:07

JVM優(yōu)化操作

2021-04-20 08:00:31

Redisson關(guān)閉訂單支付系統(tǒng)

2025-01-13 06:00:00

Go語(yǔ)言gRPC

2022-10-12 09:01:52

Linux內(nèi)核線程

2025-02-24 10:07:09

Redis節(jié)點(diǎn)遷移集群
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 日本中文字幕一区 | 国产欧美一区二区精品久导航 | 国产一区二区激情视频 | 人人种亚洲 | h视频在线看 | 日韩在线观看视频一区 | 色小姐综合网 | 一级片成人 | 久久av一区 | 91av免费看 | 久久久久久国产精品 | 美女一区二区在线观看 | 一级无毛片| 草久网| av大片| 成人一区二区三区在线观看 | 欧美综合国产精品久久丁香 | 久久成人18免费网站 | 欧美在线观看一区 | 91麻豆精品国产91久久久资源速度 | 色秀网站 | 久草热8精品视频在线观看 午夜伦4480yy私人影院 | 亚洲精品日本 | 久久99久久| www久| 久久精品日产第一区二区三区 | 日韩电影中文字幕 | 欧美日韩一区二区在线 | 国产精品一区二区不卡 | 一级片av| 欧美啪啪网站 | 欧美 中文字幕 | 久久久影院 | 一区二区三区四区av | 五月天天丁香婷婷在线中 | 一区二区在线视频 | 国产精品视频免费看 | 欧美性另类 | 国产亚洲精品美女久久久久久久久久 | 久久亚洲欧美日韩精品专区 | 亚洲激精日韩激精欧美精品 |