Java线程池参数和方法

Java线程池参数和方法

1. 线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

状态名 高 3 位 接收新任务 处理阻塞队列任务 说明
RUNNING 111 Y Y
SHUTDOWN 000 N Y 不会接收新任务,但会处理阻塞队列剩余 任务
STOP 001 N N 会中断正在执行的任务,并抛弃阻塞队列 任务
TIDYING 010 任务全执行完毕,活动线程为 0 即将进入 终结
TERMINATED 011 终结状态

2. 线程池参数

ThreadPoolExecutor executor = new ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

java线程池有四个构造方法,上面的为其中一个,参数最全的,另外三个构造方法在这个构造方法中少一到两个参数。

2.1 int corePoolSize

代表线程池中核心线程数目,也即是最多保留线程数目(当线程处于空闲状态时,核心线程不会被回收,依然存在)

2.2 int maximumPoolSize

代表最大线程数目,即当核心线程数都被占用时,工作队列中依然还有任务时,会临时创建新的线程。当解决任务后,该线程就会被摧毁。(相当于临时工)

2.3 long keepAliveTime

代表生存时间,当线程空闲时,经过指定时间后被摧毁。针对的时救急线程(临时工)

2.4 TimeUnit unit

代表keepAliveTime计量单位

2.5 BlockingQueue workQueue

代表阻塞队列即工作队列。新任务会添加到任务队列当中,等待有线程空闲获取任务。

在jdk中一般有四种队列:

2.5.1 ArrayBlockingQueue

基于数组的有界阻塞队列,指定阻塞队列大小。每当有任务时,添加致队列末尾,而线程每次从头获取任务,当任务队列满时,再添加任务就会采取拒绝策略。有界的数组可以防止资源耗尽问题。

2.5.2 LinkedBlockingQuene

基于链表的*阻塞队列,实际上上限是(Integer.MAX_VALUE)。即主要有任务,就会一直压入任务队列当中。但是只会启用核心线程来执行任务,并不会启动应急线程。因此,最好将corePoolSize和maximumPoolSize设为一致。

2.5.3 SynchronousQuene

是一个不缓存任务的阻塞队列,当存入任务来临时,必须等待线程调用。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maximumPoolSize,则执行拒绝策略。

2.5.4 PriorityBlockingQueue

具有优先级的*阻塞队列,优先级通过参数Comparator实现。

2.6 ThreadFactory threadFactory

创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

2.7 RejectedExecutionHandler handler

**拒绝策略。当任务队列中达到任务上限时,如果再继续添加就会采用拒绝策略,通过拒绝策略来处理新的任务。**不同的拒绝策略有着不同的方法,jdk中默认有四种拒绝策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

3 Executors常用工厂方法

3.1 newFixedThreadPool

评价:适用于任务量已知,相对耗时的任务

// 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
// 阻塞队列是*的,可以放任意数量的任务
Executors.newFixedThreadPool(5);
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

3.2 newCachedThreadPool

评价:整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线 程。 适合任务数比较密集,但每个任务执行时间较短的情况

// 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着
	// 全部都是救急线程(60s 后可以回收)
	// 救急线程可以无限创建
// 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

3.3 newSingleThreadExecutor

// 希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入*队列排队。任务执行完毕,这唯一的线程也不会被释放。
Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

这里需要说明下使用该方法创建的线程池(只有一个线程)和自己创建一个单线程有什么区别

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一 个线程,保证池的正常工作

使用该方法创建的线程池(只有一个线程)和使用Executors.newFixedThreadPool(1)创建线程池区别:

  • Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改

    • FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改

    • 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

4. 提交任务

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
	throws InterruptedException;

// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
	long timeout, TimeUnit unit) throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws 					InterruptedException, ExecutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

5. 关闭线程池

5.1 shutdown

/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改线程池状态
        advanceRunState(SHUTDOWN);
        // 仅会打断空闲线程
        interruptIdleWorkers();
        onShutdown(); // 扩展点 ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
    tryTerminate();
}

5.2 shutdownNow

/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改线程池状态
        advanceRunState(STOP);
        // 打断所有线程
        interruptWorkers();
        // 获取队列中剩余任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试终结
    tryTerminate();
    return tasks;
}
上一篇:linux vscode c++调试


下一篇:leetcode 第237场周赛(2021/4/18)