线程池

参考:https://www.cnblogs.com/jiawen010/p/11855768.html

 

一、线程池简介

1. 线程池的概念

  线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好的提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束后,该线程并不会死亡,而是再次返回线程池中,成为空闲状态,等待执行下一个任务。

 

2. 线程的工作机制

  在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,就把任务交给某个空间的线程。

  一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

 

3. 使用线程池的原因

  • 多线程运行时,系统不断的创建和关闭新线程,成本非常高,会过度消耗系统资源
  • 提高系统响应速度,当有任务到达时,通过复用已有的线程,无需等待新线程的创建就能立即执行
  • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生 OOM,并且会造成 cpu 过度切换,影响系统性能

 

二、线程池主要参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

  1. corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于 corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于 corePoolSize 时。除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

  2. maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于 maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略此参数。

  3. keepAliveTime(线程存活保持时间):当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

  4. workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

  5. threadFactory(线程工厂):用于创建新线程。threadFactory 创建的线程也是采用 new Thread() 方式,threadFactory 创建的线程名都有统一的风格:pool-m-thread-n (m 为线程池的编号, n 为线程池内线程编号)。

  6. handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

  

  线程池流程:判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则判断任务队列(等待线程执行的任务的队列)是否已满,没满则将新提交的任务添加在工作队列,已满则判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。

  分别判断:核心线程池 -> 工作队列 -> 整个线程池

 

三、线程池为什么要使用阻塞队列而不使用非阻塞队列

  阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入 wait 状态,释放 CPU 资源。

  当队列中有任务时,才唤醒对应线程从队列中取出消息进行执行。

  使得线程不至于一直占用 cpu 资源。

 

四、如何配置线程池

   CPU 密集型任务:尽量使用较小的线程池,一般为 CPU 核心数 +1。因为 CPU 密集型任务使得 CPU 使用率很高,若开过多的线程数,会造成 CPU 过度切换。

  IO 密集型任务:可以使用稍大的线程池,一般为 2*CPU 核心数。IO 密集型任务 CPU 使用率并不高,因此可以让 CPU 在等待 IO 的时候由其他线程去处理别的任务,充分利用 CPU 时间。

  混合型任务:可以将任务分成 IO 密集型和 CPU 密集型任务,然后分别用不同的线程池去处理。只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。因为如果划分后两个任务执行时间有数量级的差距,那么拆分没有意义。先执行完的任务要等后执行完的任务,最终的时间仍取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。(并不很理解

 

五、四种常见的线程池详解

  线程池的返回值 ExecutorService 是 Java 提供的用于管理线程池的类。该类有两个作用:控制线程数量和重用线程

1. Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用,如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务。

public class Test {
    public static void main(String[] args) {

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for(int i=0; i<10; i++){
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " 正在被执行 ");
                    try {
                        Thread.sleep(2000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

  这个线程池为无限大,当执行当前任务时,上一个任务已完成,会复用执行上一个任务的线程,而不是每次新建线程。

  感觉有点问题,在任务执行特别多速度特别慢的时候,这里的线程池中线程数量就会急剧上升

 

2. Executors.newFixedThreadPool(int i):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。

    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for(int i=0; i<10; i++){
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " 正在被执行");
                    try{
                        Thread.sleep(2000);
                    } catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }
    }

  因为线程池大小为 3,每个任务输出打印结果后 sleep 2秒,所以每两秒打印 3 个结果。

  定长线程池的大小最好根据系统资源进行设置。如 Runtime.getRuntime().availableProcessors()。

 

3. Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行

    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟 1 秒后每 3 秒执行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }

  

4. Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行。

public static void main(String[] args) {
        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        for(int i=0; i<10; i++){
            final int index = i;
            singleThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println(Thread.currentThread().getName() + " 正在执行,打印的值是:" + index);
                        Thread.sleep(500);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }
    }

  

六、缓冲队列 BlockingQueue 和自定义线程池 ThreadPoolExecutor

1. 缓冲队列 BlockingQueue 简介

  BlockingQueue 是双缓冲队列。BlockingQueue 内部使用两条队列,允许两个线程同时向列队 一个存储一个取出操作。在保证并发安全的同时,提高了队列的存取效率。

 

2. 常用的几种 BlockingQueue

  • ArrayBlockingQueue(int i):规定大小的 BlockingQueue,其构造必须指定大小。其所含的对象是 FIFO 顺序排序的
  • LinkedBlockingQueue() 或者 (int i):大小不固定的 BlockingQueue,如果其构造时指定大小,生成的 BlockingQueue 有大小限制,不指定大小,其大小由 Integer.MAX_VALUE 来决定。其所含的对象是 FIFO 顺序排序的
  • PriorityBlockingQueue() 或者 (int i):类似于 LinkedBlockingQueue,但是其所含对象的排序不是 FIFO,而是依据对象的自然顺序或者构造函数的 Comparator 决定
  • SynchronizedQueue():特殊的 BlockingQueue,对其的操作必须是放和取的交替完成

 

3. 自定义线程池(ThreadPoolExecutor 和 BlockingQueue 连用)

  自定义线程池,可以用 ThreadPoolExecutor 来创建,它有多个构造方法来创建线程池

 

public class MyThreadPoolExecutor {

    public static class Temphread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 正在执行");
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Runnable> bq = new ArrayBlockingQueue<>(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,
                6, 50, TimeUnit.SECONDS, bq);

        Runnable t1 = new Temphread();
        Runnable t2 = new Temphread();
        Runnable t3 = new Temphread();
        Runnable t4 = new Temphread();
        Runnable t5 = new Temphread();
        Runnable t6 = new Temphread();

        executor.execute(t1);
        executor.execute(t2);
        executor.execute(t3);
        executor.execute(t4);
        executor.execute(t5);
        executor.execute(t6);

        executor.shutdown();

    }
}

  

posted @ 2020-08-24 19:44  停不下的时光  阅读(222)  评论(0编辑  收藏  举报