Java多线程-jdk提供的线程池

1.线程池的状态

ThreadPoolExecutor使用int的高三位表示线程池的状态,低29为表示线程数量。

状态名称 高3位的值 描述
RUNNING 111 接收新任务,同时处理任务队列中的任务
SHUTDOWN 000 不接受新任务,但是处理任务队列中的任务
STOP 001 中断正在执行的任务,同时抛弃阻塞队列中的任务
TIDYING 010 任务执行完毕,活动线程为0时,即将进入终结阶段
TERMINATED 011 终结状态

高三位111表示的是负数:- 3

1
2
3
4
5
6
7
private static final int COUNT_BITS = 29;
private static final int COUNT_MASK = 536870911;
private static final int RUNNING = -536870912;
private static final int SHUTDOWN = 0;
private static final int STOP = 536870912;
private static final int TIDYING = 1073741824;
private static final int TERMINATED = 1610612736;

线程池状态和线程池中线程的数量由一个原子整型ctl来共同表示

2.构造方法

2.1 最全的

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
    • maximumPoolSize - corePoolSize = 救急线程数
  • keepAliveTime:救急线程空闲时的最大生存时间
  • unit:时间单位
  • workQueue:阻塞队列(存放任务)
    • 有界阻塞队列 ArrayBlockingQueue
    • 无界阻塞队列 LinkedBlockingQueue
    • 最多只有一个同步元素的 SynchronousQueue
    • 优先队列 PriorityBlockingQueue
  • threadFactory:线程工厂(给线程取名字)
  • handler:拒绝策略

救急线程就是当核心线程都在处理任务,并且阻塞队列里已经满了的时候,如果再来一个任务,急救线程就会被创建,来处理这个任务,当超过救急线程空闲时的最大生存时间,它会被释放。(当阻塞队列有容量限制时才可以使用救急线程)

拒绝策略:如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现

  • AbortPolicy:让调用者抛出 RejectedExecutionException 异常,这是默认策略
  • CallerRunsPolicy:让调用者运行任务
  • DiscardPolicy:放弃本次任务
  • DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之

2.2 newFixedThreadPool

1
2
3
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}

用法:

1
ExecutorService pool = Executors.newFixedThreadPool(2);

1
2
3
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory);
}

这个方法可以给线程起名字。

2.3 newCachedThreadPool

1
2
3
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}

用法:

1
ExecutorService pool = Executors.newCachedThreadPool();

没有核心线程,最大线程数为Integer.MAX_VALUE,所有创建的线程都是救急线程,空闲时生存时间为60秒

2.4 newSingleThreadExecutor

1
2
3
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}

用法

1
ExecutorService pool = Executors.newSingleThreadExecutor();

线程数固定为1,不能修改,没有急救线程,当任务数多于1时会放入无界队列排队,任务执行完毕这个线程也不会释放。

3.提交任务

4.定时执行任务

1
2
3
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

4.1 schedule()

1
2
3
4
5
6
pool.schedule(()->{
System.out.println("任务1执行...");
}, 1, TimeUnit.SECONDS);//延时1s之后执行
pool.schedule(()->{
System.out.println("任务2执行...");
}, 1, TimeUnit.SECONDS);//延时1s之后执行

(无论核心线程数是1还是更多)一旦第一个任务执行受到了异常,第二个任务也不会受到影响,还是会被执行。

4.2 scheduleAtFixedRate()

1
pool.scheduleAtFixedRate(()->{},1,1,TimeUnit.SECONDS);

以固定速率执行任务

第一个参数是任务对象,第二个参数是针对当前时间来说,延时的时间(主线程调用这个方法后,多长时间后才开始工作),第三个参数是执行间隔(每隔这么长时间就执行一次任务,一直执行)

当一个任务的执行时间超过了执行间隔,就等这个任务执行完后立刻执行下一个任务,不会让任务重叠。

假如每个任务执行需要耗时2s,执行间隔是1s,明显已经超过了执行间隔,每个任务之间的时间是2s,就是任务执行时间。

4.3 scheduleWithFixedDelay()

1
pool.scheduleWithFixedDelay(()->{},1,1,TimeUnit.SECONDS);

第三个参数是执行间隔(这个是真正的间隔,严格意义上每个任务之间都必须有这个间隔时间),其他的参数和上面的那个方法一样

假如每个任务执行需要耗时2s,再加上执行间隔1s,每个任务之间的时间是3s

5.结束任务

5.1 shutdown()

线程池状态变为SHUTDOWN,不会接收新任务, 但已经提交的任务(在阻塞队列中)会执行完,调用这个方法的线程在运行完这一行会继续往后执行,不会等待线程全部SHUTDOWN之后再往后执行。

1
2
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.shutdown();

5.2 shutdownNow()

线程池状态变为STOP,不会接收新任务,会将队列中的任务返回,不再执行,并用interrupt的方式中断正在执行的线程。(夺笋那)


Java多线程-jdk提供的线程池
https://vickkkyz.fun/2022/03/24/Java/JUC/官方线程池/
作者
Vickkkyz
发布于
2022年3月24日
许可协议