整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

超实用!学习dubbo如何优雅关闭线程池

超实用!学习dubbo如何优雅关闭线程池

荐阅读:

  • 腾讯T4大佬总结:微服务、SpringBoot、SpringCloud、Dubbo文档

基本方法学习

线程池是我们经常使用的工具,也是面试必问的知识点,那么如何优雅的关闭线程池那?

线程池相信大家都使用过,但是使用完成之后如何关闭线程池大家真的未必真的会使用。有人可能会说调用shutdown或者shutdownNow就可以了,真的有那么简单吗?如果你关闭线程池的姿势不正确,最严重的情况会导致线程一直存在系统中。

  • shutDown:通知线程池启动有序关闭,执行线程池之前已经提交的任务,但是不再接受新的任务。调用shutDown后再提交任务将会抛出RejectedExecutionException异常。
  • shutDownNow:尝试立即停止所有已经提交的任务,并会返回正在等待执行(未执行)的任务列表。shutDownNow通过向线程池中的线程发送一个中断请求而中止线程,如果线程池中运行了会抛出InterruptedException的程序,将会抛出一个InterruptedException。如过这个线程不能响应中断那么可能永远无法被终止。
  • isTerminated:所有的任务都被关闭返回true,否则返回false。只有调用了shutDown或者shutDownNow,isTerminated才可能为true。
  • awaitTermination(long timeout, TimeUnit unit) throws InterruptedException:阻塞当前线程直到 所有任务执行完毕 或者超时 或者当前线程被中断 如果所有任务都关闭,则返回true,否则返回false。

优雅关闭线程池的正确姿势

  • step1:执行shutdown方法,等待所有任务执行完毕并拒绝新任务的提交。
  • step2:执行awaitTermination(long timeout,TimeUnit unit),指定超时时间,判断是是否已经关闭所有任务,防止线程永远无法关闭。
  • step3:如果step2返回fasle,或者被中断。调用shutDownNow方法立即关闭线程池所有任务。

dubbo关闭线程池工具类学习

public class ExecutorUtil {
    private static final Logger logger=LoggerFactory.getLogger(ExecutorUtil.class);
    private static final ThreadPoolExecutor shutdownExecutor=new ThreadPoolExecutor(0, 1,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(100),
            new NamedThreadFactory("Close-ExecutorService-Timer", true));

    public static boolean isTerminated(Executor executor) {
        if (executor instanceof ExecutorService) {
            if (((ExecutorService) executor).isTerminated()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Use the shutdown pattern from:
     *  https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
     * @param executor the Executor to shutdown
     * @param timeout the timeout in milliseconds before termination
     */
    public static void gracefulShutdown(Executor executor, int timeout) {
        if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
            return;
        }
        final ExecutorService es=(ExecutorService) executor;
        try {
            // Disable new tasks from being submitted
            es.shutdown();
        } catch (SecurityException ex2) {
            return;
        } catch (NullPointerException ex2) {
            return;
        }
        try {
            // Wait a while for existing tasks to terminate
            if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
                es.shutdownNow();
            }
        } catch (InterruptedException ex) {
            es.shutdownNow();
            Thread.currentThread().interrupt();
        }
        if (!isTerminated(es)) {
            newThreadToCloseExecutor(es);
        }
    }

    public static void shutdownNow(Executor executor, final int timeout) {
        if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
            return;
        }
        final ExecutorService es=(ExecutorService) executor;
        try {
            es.shutdownNow();
        } catch (SecurityException ex2) {
            return;
        } catch (NullPointerException ex2) {
            return;
        }
        try {
            es.awaitTermination(timeout, TimeUnit.MILLISECONDS);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
        if (!isTerminated(es)) {
            newThreadToCloseExecutor(es);
        }
    }

    private static void newThreadToCloseExecutor(final ExecutorService es) {
        if (!isTerminated(es)) {
            shutdownExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i=0; i < 1000; i++) {
                            es.shutdownNow();
                            if (es.awaitTermination(10, TimeUnit.MILLISECONDS)) {
                                break;
                            }
                        }
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    } catch (Throwable e) {
                        logger.warn(e.getMessage(), e);
                    }
                }
            });
        }
    }
}


作者:克里斯朵夫李维
链接:https://juejin.im/post/5e9ec57df265da47c9171456

avaScript 的 Event Loop(事件循环)是 JavaScript 运行时环境(如浏览器和 Node.js)的核心机制之一,它使得 JavaScript 能够处理异步操作而不会阻塞程序的执行。了解 Event Loop 对于理解 JavaScript 的非阻塞行为和编写高效的异步代码至关重要。

1. JavaScript 是单线程的

首先,重要的是要理解 JavaScript 是一种单线程的语言。这意味着 JavaScript 在同一时间内只能执行一个任务。然而,JavaScript 需要能够处理各种异步操作(如 AJAX 请求、文件读取、用户交互等),这些操作可能会花费很长时间完成。为了解决这个问题,JavaScript 采用了 Event Loop 和 Callback Queues(回调队列)。

2. 调用栈(Call Stack)

调用栈是 JavaScript 代码执行时的数据结构,用于存储函数调用和返回地址。每当一个函数被调用时,它就会被推入调用栈,并在函数执行完毕后从栈中弹出。如果调用栈满了(即达到了最大调用深度),则会发生栈溢出错误。

3. 堆(Heap)

堆是用于存储对象、数组等引用类型的内存区域。与调用栈不同,堆是动态分配的,并且其大小不是固定的。

4. Web APIs

Web APIs 是浏览器提供的一组与浏览器功能交互的接口,如 DOM 操作、网络请求等。这些 API 通常是异步的,并且它们有自己的线程或进程来处理请求。

5. 任务队列(Task Queue)和微任务队列(Microtask Queue)

当异步操作完成时(如 AJAX 请求、setTimeout、Promise 解决等),相应的回调函数会被放入任务队列(或称为宏任务队列)或微任务队列中。任务队列中的任务在当前的执行栈清空后才会被执行,而微任务队列中的任务会在当前执行栈清空后、但下一个宏任务执行前立即执行。

6. Event Loop 的工作原理

Event Loop 的工作流程可以概括为以下几个步骤:

  1. 检查调用栈:如果调用栈为空,则继续;如果调用栈不为空,则等待直到调用栈为空。
  2. 执行微任务队列:一旦调用栈为空,Event Loop 就会查看微任务队列是否有任务。如果有,它会依次执行微任务队列中的所有任务,然后再回到第一步。
  3. 执行宏任务队列:在所有微任务都执行完毕后,Event Loop 会从宏任务队列中取出一个任务放入调用栈执行。这个过程会不断重复。

7. 常见的宏任务和微任务

  • 宏任务(Macrotasks):包括 script(整体代码)、setTimeout、setInterval、setImmediate(Node.js 环境)、I/O、UI rendering 等。
  • 微任务(Microtasks):包括 Promise.then、Promise.catch、Promise.finally、MutationObserver、process.nextTick(Node.js 环境)等。

实例 1:setTimeout 和 Promise

console.log('1');  
  
setTimeout(()=> {  
  console.log('setTimeout 宏任务队列');  
}, 0);  
  
new Promise((resolve)=> {  
  console.log('Promise 立即执行');  
  resolve();  
}).then(()=> {  
  console.log('then 微任务队列');  
});  
  
console.log('2');

//输出顺序
1  
Promise 立即执行 
2  
then  微任务队列
setTimeout 宏任务队列

解释

  1. 首先,执行同步代码,输出 1。
  2. 然后,setTimeout 被调用,但因为它是一个宏任务,所以它的回调函数被放入宏任务队列中等待。
  3. 接下来,new Promise 的构造函数被调用,立即执行并输出 Promise。resolve() 被调用,但 .then() 中的回调函数是异步的,并且是一个微任务,所以它会被放入微任务队列中。
  4. 同步代码继续执行,输出 2。
  5. 当所有同步代码执行完毕后,Event Loop 开始处理微任务队列。它找到 .then() 的回调函数并执行,输出 then。
  6. 最后,当微任务队列为空时,Event Loop 转到宏任务队列,执行 setTimeout 的回调函数,输出 setTimeout。

实例 2:多个 Promise 和 setTimeout

console.log('1');  
  
setTimeout(()=> {  
  console.log('setTimeout  宏任务队列1');  
  new Promise((resolve)=> {  
    console.log('Promise in setTimeout');  
    resolve();  
  }).then(()=> {  
    console.log('then in setTimeout');  
  });  
  setTimeout(()=> {  
    console.log('setTimeout 宏任务队列2');  
  }, 0);  
}, 0);  
  
new Promise((resolve)=> {  
  console.log('Promise 立即执行1');  
  resolve();  
}).then(()=> {  
  console.log('then 微任务队列1');  
  new Promise((resolve)=> {  
    console.log('Promise 立即执行2');  
    resolve();  
  }).then(()=> {  
    console.log('then 微任务队列2');  
  });  
});  
  
console.log('2');

//输出顺序
1 
Promise 立即执行1  
2  
then 微任务队列1 
Promise 立即执行2  
then 微任务队列2
setTimeout  宏任务队列1  
Promise in setTimeout  
then in setTimeout  
setTimeout  宏任务队列2

解释

  1. 同步代码首先执行,输出 1、Promise 1 和 2。
  2. .then() 中的回调函数作为微任务被加入微任务队列。
  3. 第一个 setTimeout 被调用,它的回调函数被加入宏任务队列。
  4. 当所有同步代码执行完毕后,开始执行微任务队列中的任务。首先输出then 微任务队列1,然后执行 Promise 立即执行2then 微任务队列2
  5. 微任务队列为空后,执行宏任务队列中的第一个任务(即第一个 setTimeout 的回调函数),输出相关日志。
  6. 第二个 setTimeout 的回调函数也被加入宏任务队列,并在当前宏任务执行完毕后执行。

实例 3:async/await 与 Promise

const async1=async ()=> {
   console.log('async1 1');  
   await async2();  
   console.log('async1 2');  
}
   
const async2=async ()=> {
   console.log('async2');  
}
  
console.log('1');  
setTimeout(()=> {  
  console.log('setTimeout 宏任务队列');  
}, 0);  
  
async1();  
  
new Promise((resolve)=> {  
  console.log('promise 立即执行');  
  resolve();  
}).then(()=> {  
  console.log('then 微任务队列');  
});  
  
console.log('2');

//输出顺序
1
async1 1
async2  
promise 立即执行
2
async1 2
then 微任务队列
setTimeout 宏任务队列

解释

  1. 同步代码首先执行,输出1。
  2. async1() 被调用,输出async1 1。
  3. await async2() 暂停 async1() 的执行,async2() 被调用并输出 async2。因为 async2() 没有返回 Promise 或没有等待的异步操作,所以 await 后面的代码在 async2() 执行完毕后继续执行。
  4. 同步代码首先执行,输出promise 立即执行和2。
  5. 之后async2执行完毕后,同步代码输出async1 2,
  6. 当所有同步代码执行完毕后,开始执行微任务队列中的任务then 微任务队列
  7. 最后执行宏任务队列,输出setTimeout 宏任务队列

结论

Event Loop 是 JavaScript 异步编程的基石,它使得 JavaScript 能够在不阻塞主线程的情况下处理各种异步操作。通过理解 Event Loop 的工作原理,我们可以更加高效地编写异步代码,避免潜在的错误和性能问题。

一步:解析 HTML

  1. 解析过程中遇到 CSS,解析 CSS;遇到 JS ,执行 JS;为了提高效率,浏览器在开始解析之前,会启动一个预解析线程,率先下载 HTML 中外部的 CSS 文件和外部的 JS 文件;
  2. 如果主线程解析到 link位置时,此时外部的 CSS 文件还没有下载解析完成,主线程不会等待,继续执行后续代码,因为 CSS 下载和解析工作是在预解析线程中进行的,这也是CSS不会阻塞 HTML 解析的根本原因
  3. 如果主线程解析到 script 位置时;会停止解析 HTML,而去等待 JS文件下载完毕,并将全部代码执行完成后,才会继续解析 HTML;这是因为 JS 代码在执行的过程中有可能会修改当前的DOM树;所以DOM 树的生成必须停止,这也就是 JS 会阻塞HTML 解析的根本原因;

第二步:样式计算

  1. 主线程会遍历得到的 DOM 树;依次为每个 DOM 树的节点计算出最终样式;
  2. 在这一过程中,很多相对值会变成绝对值;例如:16进制颜色值会变成rgb颜色值,rem、em、vw会变成px;

  1. 第三步:布局
  2. 布局阶段主线程会依次遍历 DOM 树的每一个节点,计算每一个节点的几何信息;例如节点的宽、高、相对包含块的位置;
  3. 大部分的时候 DOM(元素) 树和 Layout(布局)树并非一一对应;
  4. 例如display:none的节点是没有几何信息的;因此不会产生Layout树、使用了伪元素的选择器、虽然DOM树中不存在这些伪元素节点,但是它们拥有几何信息,所以会生成Layout树、还有匿名行盒、匿名块盒都会导致DOM树和Layout树无法一一对应;
  5. 内容必须在行盒中,盒子的类型有CSS来决定,不由HTML来决定,HTML只提供语义化;

第四步:分层

  1. 主线程会通过一套复杂的策略来对整个Layout树进行分层;
  2. 分层的好处在于将来某一个层改变后,仅会对该层进行后续处理,不会影响到其它层;从而达到提升效率
  3. 滚动条、堆叠上下文、transform、opacity等样式都会或多或少的影响分层结果,也可以通过will-嫦娥属性来更大程度上影响分层结果。

第五步:绘制

  1. 主线程会为每个层单独产生绘制指令集,用于描述这一层的内容如何画出来;
  2. 绘制完成后,主线程将每个图层的绘制信息提交给 合成线程,剩余的工作将由合成线程完成。

第六步:分块

  1. 合成线程首先对每个图层进行分块,将其划分为更多的小区域;
  2. 它会从线程池中拿取更多的线程来完成分块工作;

第七步:光栅化

  1. 分块完成后,合成线程将块信息交给GPU进程,以极高的速度完成光栅化;
  2. GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块
  3. 光栅化的结果就是一块一块的位置;

第八步:

  1. 合成线程拿到层,每个块的位图后,生成一个个【指引(quad)】信息;
  2. 指引会标识每个位图感应该画到屏幕的哪个位置,以及会考虑旋转,缩放等变形;
  3. 变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因;
  4. 合成线程会把quad提交给GPU进程,由GPU进程产生系统调用,提交给GPU硬件,完成最终的屏幕成像。