线程池的七种创建方式

什么是线程池?

  • 线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

  • 池化思想在计算机的应用也比较广泛,比如以下这些:

    1. 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
    2. 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
    3. 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。
  • 线程池的优势主要体现在以下 4 点:

    1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
    2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
    3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
    4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池 ScheduledThreadPool,就允许任务延期执行或定期执行。

线程池的使用

  • 线程池的创建方法总共有 7 种,但总体来说可分为 2 类:
    1. 一类是通过 ThreadPoolExecutor 创建的线程池;
    2. 另一个类是通过 Executors 创建的线程池。
    package java.util.concurrent;
    public class Executors {}
    public class ThreadPoolExecutor extends AbstractExecutorService {}
    
  • 线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):
    1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
    2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
    3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
    4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
    5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
    6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
    7. ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

ExecutorService 常用方法

public interface Executor {
    //在将来的某个时间执行给定的命令。该命令可以在新线程,线程池或调用线程中执行,由 Executor实现。
    void execute(Runnable command);
}
public interface ExecutorService extends Executor {
    void execute​(Runnable command) 在将来的某个时间执行给定的命令。  
    boolean awaitTermination​(long timeout, TimeUnit unit) 阻止所有任务在关闭请求完成后执行,或发生超时或当前线程中断,以先到者为准。  
    <T> List<Future<T>> invokeAll​(Collection<? extends Callable<T>> tasks) 执行给定的任务,返回持有他们的状态和结果的所有完成的期货列表。  
    <T> List<Future<T>> invokeAll​(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,返回在所有完成或超时到期时持有其状态和结果的期货列表,以先发生者为准。  
    <T> T invokeAny​(Collection<? extends Callable<T>> tasks) 执行给定的任务,返回成功完成的结果(即,不抛出异常),如果有的话。  
    <T> T invokeAny​(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,返回一个已经成功完成的结果(即,不抛出异常),如果在给定的时间超过之前有的话。  
    boolean isShutdown​() 如果这个执行者已被关闭,则返回 true 。  
    boolean isTerminated​() 如果所有任务在关闭后完成,则返回 true 。  
    void shutdown​() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。  
    List<Runnable> shutdownNow​() 尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。  
    Future<?> submit​(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。  
    <T> Future<T> submit​(Runnable task, T result) 提交一个可运行的任务执行,并返回一个表示该任务的未来。  
    <T> Future<T> submit​(Callable<T> task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。
}

1. FixedThreadPool

  • 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
    //nThreads - 池中的线程数
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    //nThreads - 池中的线程数  threadFactory - 创建新线程时使用的工厂
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    
  • 练习
    @Test
    public  void fixedThreadPool() {
        //创建 2个数据级的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        //创建线程
        Thread thread = new Thread(() -> {
            System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
        });
        //线程池执行任务(一次添加 4个任务)
        //执行线程的方法有两种:submit 和 execute
        threadPool.submit(thread);
        threadPool.execute(thread);
        threadPool.submit(thread);
        threadPool.execute(thread);
    }
    
    image

2. CachedThreadPool

  • 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    //threadFactory - 创建新线程时使用的工厂 
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
    
  • 练习
    @Test
    public  void cachedThreadPoll() {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            cachedThreadPool.execute(() -> {
                System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
            });
        }
    }
    
    image
    • 从上述结果可以看出,线程池创建了 10 个新线程来执行相应的任务。

3. SingleThreadExecutor

  • 创建单个线程数的线程池,它可以保证先进先出的执行顺序。
    //创建一个使用从无界队列运行的单个工作线程的执行程序。 
    //(请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务,
    //则新的线程将占用它。)任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。 
    //与其他等效的newFixedThreadPool(1)不同,返回的执行器保证不被重新配置以使用额外的线程。 
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
    
  • 练习
    @Test
    public void SingleThreadPool() {
        ExecutorService singleThreadPoll = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            singleThreadPoll.execute(() -> {
                System.out.println(finalI + ":任务被执行");
            });
        }
    }
    
    image

4. ScheduledThreadPool

  • 创建一个可以执行延迟任务的线程池。
    //创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
    
  • 练习
  • 必须在 main方法上才可以定时,junit不行的
    public static void scheduledThreadPool() {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
        //添加定时任务执行(5s)
        System.out.println("添加任务,时间:" + new Date());
        threadPool.schedule(() -> {
            System.out.println("任务被执行,时间:" + new Date());
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 5, TimeUnit.SECONDS);
    }
    
    image

5. SingleThreadScheduledExecutor

  • 创建一个单线程的可以执行延迟任务的线程池。
    //创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。 
    //(请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,
    //则如果需要执行后续任务,则新的线程将占用它。)任务保证顺序执行,并且不超过一
    //个任务将被激活在任何给定的时间。 与其他等效的newScheduledThreadPool(1)不同
    //,返回的执行器保证不被重新配置以使用额外的线程。 
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1, threadFactory));
    }
    
  • 练习
  • 必须在 main方法上运行,junit不行
        public static void singleThreadScheduledPoll() {
        ScheduledExecutorService threadPool =
                Executors.newSingleThreadScheduledExecutor();
        //添加定时任务执行(5s)
        System.out.println("添加任务,时间:" + new Date());
        threadPool.schedule(() -> {
            System.out.println("任务被执行,时间:" + new Date());
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 5, TimeUnit.SECONDS);
    }
    
  • image

6. newWorkStealingPool

  • 创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用。
    //创建一个维护足够的线程以支持给定的并行级别的线程池,并且可以使用多个队列来减少争用。
    //并行级别对应于主动参与或可以从事任务处理的最大线程数。 线程的实际数量可以动态增长和收缩。
    //工作窃取池不保证执行提交的任务的顺序。
    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
  • 练习
    @Test
    public  void newWorkStealingPoll() {
        ExecutorService threadPool = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            threadPool.execute(() -> {
                System.out.println(finalI + ":被执行,线程:" + Thread.currentThread().getName());
            });
        }
    }
    
    image
    • 从上述结果可以看出,"任务的执行顺序是不确定的,因为它是抢占式执行的"。

7. ThreadPoolExecutor

  • 最原始的创建线程池的方式,它包含了 7 个参数可供设置(阿里推荐使用的)。
    image
    public ThreadPoolExecutor(
        //核心线程池大小,即使他们处于空闲状态,除非设置 allowCoreThreadTimeOut(允许核心线程超时) 
        int corePoolSize,
        //线程池最大容量大小
        int maximumPoolSize,
        //最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
        long keepAliveTime,
        //keepAliveTime参数的时间单位(天,时,分,秒,毫秒,微秒,纳秒)
        TimeUnit unit,
        //一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型。
        //线程的排队策略与 BlockingQueue有关
        //ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
        //LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
        //SynchronousQueue:(常用)一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
        //PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
        //DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
        //LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
        //LinkedBlockingDeque:(常用)一个由链表结构组成的双向阻塞队列。
        BlockingQueue<Runnable> workQueue,
        //当执行程序创建新线程使用的工厂(默认为正常优先级,非守护线程)
        ThreadFactory threadFactory,
        //(拒绝策略4中)执行被阻止时使用的程序,因为线程限制和队列容量
        //AbortPolicy:(默认)拒绝并抛出异常。
        //CallerRunsPolicy:使用当前调用的线程来执行此任务。
        //DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
        //DiscardPolicy:忽略并抛弃当前任务。
        RejectedExecutionHandler handler
    }
    
  • 练习
    public static void threadPool() {
        ThreadPoolExecutor threadPool =
                new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            threadPool.execute(() -> {
                System.out.println(finalI + ":被执行,线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);//1000MS
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
    
    image

线程池的执行流程

https://www.cnblogs.com/javazhiyin/p/11364027.html
http://www.codebaoku.com/tech/tech-yisu-465674.html

  • corePoolSizemaximumPoolSizeworkQueue之间关系。
    • 当前线程池中线程数小于 corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
    • 当线程池数达到 corePoolSize时,新提交任务将被放入 workQueue中,等待线程池中任务执行。
    • 当 workQueue已满,且 maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。
    • 当 workQueue已满,且提交任务超过 maximumPollSize,任务由 RejectedExecutionHandler处理。
    • 当线程池中线程数超过 corePoolSize,且超过这部分的空闲时间达到 keepAliveTime时,回收这些线程。
    • 当设置 allowCoreThreadTimeOut(true)时,线程池中 corePollSize范围内的线程空闲时间达到 keepAliveTimer也将回收。
  • 执行流程
    image
    1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行他们。
    2. 当调用 execute()方法添加一个任务时,线程池会做如下判断
      a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建运行这个队列。
      b. 如果正在运行的线程数量大于或者等于 corePoolSize,那么将这个任务放入队列。
      c. 如果这时候队列满了,而且正在运行的线程数量小于 maximunPollSize,那么线程池会抛出 RejectedExecutionException。
    3. 当一个线程完成任务时,他会从队列中取下一个任务来执行。
    4. 当一个线程无事可做,超过一定时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有的任务完成后,他最终会收缩到 corePoolSize的大小。

线程拒绝策略

https://www.cnblogs.com/skywang12345/p/3512947.html#a21

  • AbortPolicy
    当任务添加到线程池中被拒绝时,他将抛出 RejectedExecutionException 异常。
  • CallerRunsPolicy
    当任务添加到线程池中被拒绝时,会在线程池当前正在运行的 Thread线程池中处理被拒绝的任务。
  • DiscardOldestPolicy
    当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的末处理任务就,然后将被拒绝的任务添加到等待队列中。
  • DiscardPolicy
    当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。
  • 练习 DiscardPolicy
    public class DiscardPolicyDemo {
        private static final int CORE_POOL_SIZE = 1;//核心线程数
        private static final int MAXIMUM_POOL_SIZE = 1;//最大线程数
        private static final int CAPACITY = 1;//容量
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
                    ///最大线程数可以存活的时间
                    0,
                    TimeUnit.SECONDS,
                    //数组结构组成的有界阻塞队列
                    new ArrayBlockingQueue<Runnable>(CAPACITY));
            //设置线程池的拒绝策略为“丢弃”
            threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                threadPool.execute(() -> {
                    System.out.println(finalI + " : " + Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
    
        }
    }
    
    image
    • 结果说明:线程池 pool的核心线程数和最大线程都为1这意味着"线程池能同时运行的任务数量最大只能是1"。线程池pool的阻塞队列是ArrayBlockingQueue,ArrayBlockingQueue是一个有界的阻塞队列,ArrayBlockingQueue的容量为1。这也意味着线程池的阻塞队列只能有一个线程池阻塞等待。根据""中分析的execute()代码可知:线程池中共运行了2个任务。第1个任务直接放到Worker中,通过线程去执行;第2个任务放到阻塞队列中等待。其他的任务都被丢弃了!

自定义拒绝策略

public class CustomRefuseHandler {
    public static void main(String[] args) {
        // 任务的具体方法
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("当前任务被执行,执行时间:" + new Date() +
                        " 执行线程:" + Thread.currentThread().getName());
                try {
                    // 等待 1s
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 创建线程,线程的任务队列的长度为 1
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // 执行自定义拒绝策略的相关操作
                        System.out.println("我是自定义拒绝策略~");
                    }
                });
        // 添加并执行 4 个任务
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
    }
}

image

https://www.cnblogs.com/vipstone/p/14149065.html

posted @ 2022-06-24 10:02  MikiKawai  阅读(174)  评论(0编辑  收藏  举报