Java并发编程——线程池

Posted jet-strong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程——线程池相关的知识,希望对你有一定的参考价值。

1、线程池工作原理

  Java线程池主要用于管理线程组及其运行状态。其主要作用是线程复用、线程资源管理、控制操作系统的最大并发数。

  Java线程池的工作原理:JVM先根据用户的参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量,则超出数量的线程排队等候,在有任务执行完毕后,线程池调度器会发现有可用的线程,进而再次从队列中取出任务并执行。

1.1 线程池的核心组件

  线程池主要有4个核心组件组成:

  • 线程池管理器:用于创建并管理线程池。
  • 工作线程:线程池中执行具体任务的线程。
  • 任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口任务才能被线程池调度。
  • 任务队列:存放待处理任务,新的任务将会不断被加入队列中,执行完成的任务将被从队列中移除。

1.2 线程池的核心类

  Java线程池是通过Executor框架实现,该框架的核心类包括:Executor、Executors,ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTash。

具体继承关系如下:

  其中,ThreadPoolExecutor是构建线程的核心构造方法,该方法的定义如下:

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

  ThreadPoolExecutor构造函数的具体函数如下表:

参数 说明
corePoolSize
线程池中核心线程的数量
maximumPoolSize
线程池中最大线程的数量
keepAliveTime
当前线程数量超过corePoolSize时,空闲线程的存活时间
unit
keepAliveTime的时间单位
workQueue
任务队列,被提交但未被执行的任务存放的地方
threadFactory
线程工厂,用于创建线程,可使用默认的线程工厂或者自定义线程工厂
handler
任务拒绝策略,由于任务过多或其他原因导致线程池无法处理时使用的任务拒绝策略

 

 

 

 

 

 

 

 

 

 

 

1.3 线程池的工作流程

  Java线程池的工作流程为:

  1. 线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加任务时,线程池开始按流程执行任务。
  2. 如果正在运行的线程数量少于corePoolSize,线程池就会立刻创建线程并执行该线程任务。
  3. 如果正在运行的线程数量大于等于corePoolSize,该任务将被放入阻塞队列中。
  4. 在阻塞队列已满且正在运行的线程数量小于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。
  5. 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。
  6. 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
  7. 在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。

具体流程如图:

1.4 线程池的拒绝策略

  JDK内置的拒绝策略有AboutPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy这4种,也可以自定义拒绝策略。

  1、AboutPolicy

  AboutPolicy直接抛出异常,阻止线程正常运行。

具体的JDK源码如下:

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }

        //直接抛出异常信息,不做任何处理
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
    }
}

  2、CallerRunsPolicy

  CallerRunsPolicy的拒绝策略为:如果被丢弃的线程任务未关闭,则执行该线程任务。其实CallerRunsPolicy拒绝策略不会真的丢弃任务。

具体的JDK源码如下:

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
       
        public CallerRunsPolicy() { }

        //执行被丢弃的任务
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

  3、DiscardOldestPolicy

  DiscardOldestPolicy的拒绝策略为:移除线程队列中最早的一个线程任务,并尝试提交当前任务。

具体的JDK源码如下:

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        
        public DiscardOldestPolicy() { }

        
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();//丢弃线程队列中最老的一个线程任务
                e.execute(r);//尝试提交当前任务
            }
        }
    }

  4、DiscardPolicy

  DiscardPolicy的拒绝策略为:丢弃当前的线程任务而不做任何处理。如果系统允许在资源不足的情况下丢弃部分任务,可以保证系统安全和稳定。

具体的JDK源码如下:

    public static class DiscardPolicy implements RejectedExecutionHandler {
       
        public DiscardPolicy() { }

        //直接丢弃线程,不做任何处理
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

 

2、5种常用线程池

  ExecutorService接口有多个实现类可用于创建不同的线程池。如下是5种常用的线程池。

名称 说明
newCachedThreadPool 可缓存的线程池
newFixedThreadPool 固定大小的线程池
newScheduledThreadPool 可做任务调度的线程池
newSingleThreadExecutor 单个线程的线程池
newWorkStealingPool 足够大的线程池,JDK1.8新增

 

 

 

 

 

 

 

 

2.1 newCachedThreadPool

  newCachedThreadPool用于创建一个缓存线程池。其在创建新线程时,如果有可重用线程,则重用这些线程,否则重新创建一个新的线程并将其添加到线程池中。

  newCachedThreadPool适用于执行时间很短的大量任务的情况。

创建方式如下:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

2.2 newFixedThreadPool

  newFixedThreadPool用于创建一个固定线程数量的线程池,并将线程资源存放在队列中循环使用。

  在newFixedThreadPool线程池中,如果活动状态的线程数量大于等于核心线程池线程数量,则新提交的任务将在阻塞队列中排队,直到有可用的线程资源。

创建方式如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

2.3 newScheduledThreadPool

  newScheduledThreadPool创建一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务。

创建方式如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//创建一个延迟3秒执行的线程
scheduledThreadPool.schedule(myRunnable,3, TimeUnit.SECONDS);
//创建一个延迟1秒执行且每3秒执行一次的线程
scheduledThreadPool.scheduleAtFixedRate(myRunnable,1,3,TimeUnit.SECONDS);

2.4 newSingleThreadExecutor

  newSingleThreadExecutor线程池永远保证有且仅有一个可用线程,即便该线程停止或发生异常,newSingleThreadExecutor会启动一个新的线程来代替该线程。

创建方式如下:

ExecutorService singleThread = Executors.newSingleThreadExecutor();

2.5 newWorkStealingPool

  newWorkStealingPool创建足够的线程的线程池以达到快速运算的目的。JDK根据当前线程的运行需求向操作系统申请足够的线程。

创建方式如下:

ExecutorService workStealingPool = Executors.newWorkStealingPool();

以上是关于Java并发编程——线程池的主要内容,如果未能解决你的问题,请参考以下文章

Java并发多线程编程——线程池

并发编程精华问答| Java线程池使用时注意事项

并发编程精华问答| Java线程池使用时注意事项

Java 并发编程线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )

Java并发编程——线程池

Java并发编程——线程池