线程池的优点及其原理

  • 一 使用线程池的好处

    池化技术应用:线程池、数据库连接池、http连接池等等。

    池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

    线程池提供了一种限制、管理资源的策略。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

    使用线程池的好处:

    • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。

    • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。

     

    二 Executor框架

    Executor框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,让并发编程变得更加简单。

    Executor框架的使用示意图

    1. 主线程首先要创建实现Runnable或Callable接口的任务对象。

    2. 把创建完成的实现Runnable/Callable接口的对象直接交给ExecutorService执行:

      ExecutorService.execute(Runnable command)或者ExecutorService.sumbit(Runnable command)或ExecutorService.sumbit(Callable <T> task).
    3. 如果执行ExecutorService.submit(...),ExecutorService将返回一个实现Future接口的对象。最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel()来取消次任务的执行。

    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class ThreadPoolExecutorDemo {
    
        private static final int CORE_POOL_SIZE = 5;
        private static final int MAX_POOL_SIZE = 10;
        private static final int QUEUE_CAPACITY = 100;
        private static final Long KEEP_ALIVE_TIME = 1L;
    
        public static void main(String[] args) {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    CORE_POOL_SIZE,
                    MAX_POOL_SIZE,
                    KEEP_ALIVE_TIME,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                    new ThreadPoolExecutor.CallerRunsPolicy());
    
            //执行线程代码
    
            executor.shutdown();
    
        }
    
    }
     

    CORE_POOL_SIZE:核心线程数定义了最小可以同时运行的线程数量。

    MAX_POOL_SIZE:当队列中存放的任务到达队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

    QUEUE_CAPACITY:当新任务加入是会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放到队列中。

    KEEP_ALIVE_TIME:当线程池中的线程数量大于核心线程数时,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过KEEP_ALIVE_TIME才会被回收销毁。

    ThreadPoolExecutor.CallerRunsPolicy():调用执行自己的线程运行任务,也就是直接在调用execute方法的线程运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃任务。因此这种策略会降低新任务的提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果应用程序可以承受此延迟并且不能任务丢弃一个任务请求的话,可以选择这个策略。

     

    线程池分析原理

     

    三 线程池大小确定

    有一个简单且使用面比较广的公式:

    • CPU密集型任务(N+1):这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核心数)+1,比CPU核心数多出来一个线程是为了防止线程偶发的缺页中断,或者其他原因导致的任务暂停而带来的影响。一旦任务停止,CPU就会出于空闲状态,而这种情况下多出来一个线程就可以充分利用CPU的空闲时间。

    • I/O密集型(2N):这种任务应用起来,系统大部分时间用来处理I/O交互,而线程在处理I/O的是时间段内不会占用CPU来处理,这时就可以将CPU交出给其他线程使用。因此在I/O密集型任务的应用中,可以配置多一些线程,具体计算方是2N。

posted @ 2019-12-26 12:42  allenhongw  阅读(8799)  评论(0编辑  收藏  举报