文章目录
一、ThreadPoolExecutor
1. 线程池类关系图
- Executor接口:是一个根接口,声明了execute(Runnable runnable)方法,执行任务代码
public interface Executor {
void execute(Runnable command);
}
- ExecutorService接口:是线程的主要接口,继承Executor接口,声明方法:submit、invokeAll、invokeAny、shutdown等方法
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
- AbstractExecutorService 抽象类:实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
......
}
- ScheduledExecutorService接口:继承ExecutorService接口,声明定时执行任务方法
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
- ThreadPoolExecutor类:是线程池的核心实现类,用来执行被提交的任务,继承AbstractExecutorService 类,实现execute、submit、shutdown、shutdownNow等方法
public class ThreadPoolExecutor extends AbstractExecutorService {
}
- ScheduledThreadPoolExecutor类:可以进行延迟或者定期执行任务,继承ThreadPoolExecutor类,实现ScheduledExecutorService接口并实现其中的方法
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
}
2. 构造方法(含7个核心参数)
- 带7个参数的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
{...}
- corePoolSize 核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
- maximumPoolSize 最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
- keepAliveTime 非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
- unit 时间单位 - keepAliveTime的时间单位
- workQueue 用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
- threadFactory 创建线程的工厂类,可以为线程创建时起名字,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
- handler 拒绝策略,线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
3. 线程池工作方式
- 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务
- 当线程数达到 corePoolSize 并且没有线程空闲时,这时再新加的任务会被加入 workQueue 队列排
队,直到有空闲的线程 - 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的非核心线程。
- 如果线程到达 maximumPoolSize 后,仍然有新任务加入,这时会执行拒绝策略
- 拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
- AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
- CallerRunsPolicy 让调用者运行任务
- DiscardPolicy 放弃本次任务
- DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
其它框架的拒绝策略
- Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
- Netty 的实现,是创建一个新线程来执行任务
- ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
- PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
当高峰过去后,超过corePoolSize 的非核心线程如果一段时间没有任务做,需要结束非核心线程来节省资源,这个时间由 keepAliveTime 和 unit 来控制
二、Executors
1. newFixedThreadPool
(1) 源码描述
- 创建一个线程池,该线程池重用固定数量的线程在共享的无界队列中运行。
- 在任何时候,最多有 nThreads 个线程是活动的处理任务。
- 如果在所有线程都处于活动状态时提交了额外的任务,它们将在队列中等待,直到有线程可用。
- 如果任何线程在关闭前的执行过程中由于失败而终止,则在需要执行后续任务时,将有一个新线程代替它。
- 线程池中的线程将一直存在,直到它被明确关闭。
源码:
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
(2) 特点
- 核心线程数 == 最大线程数,没有非核心线程被创建,因此也无需超时时间(keepAliveTime=0)
- 阻塞队列LinkedBlockingQueue是无界的,可以放任意数量的任务
- 适用于任务量已知,相对耗时的任务
- 当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常
(3) 测试
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
// 自定义线程工厂
private AtomicInteger t = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "mypool_t" + t.getAndIncrement());
}
});
pool.execute(() -> {
log.debug("1");
});
pool.execute(() -> {
log.debug("2");
});
pool.execute(() -> {
log.debug("3");
});
}
执行完任务,线程池没有关闭
2. newCachedThreadPool
(1) 描述
- 创建一个线程池,根据需要创建新线程,但在可用时将重用先前构造的线程。
- newCachedThreadPool线程池通常会提高执行许多短期异步任务程序的性能。
- 如果有可用线程,调用 execute 将重用先前构造的线程。
- 如果没有可用的现有线程,则会创建一个新线程并将其添加到线程池中。
- 60 秒内未使用的线程将被终止并从缓存中删除。因此,保持空闲时间足够长的线程池不会消耗任何资源。
- 请注意,可以使用 ThreadPoolExecutor 构造函数创建具有相似属性但不同细节(例如,超时参数)的线程池。
源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(2) 特点
- 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE(全部都是临时线程),临时线程的空闲生存时间是 60s
- 使用的是SynchronousQueue队列,没有容量,没有线程来取,任务是放不进去的
- 注意:因为SynchronousQueue不能存放任务,所以任务过多时,会创建很多临时线程,可能会出现 OOM 或 CPU 100%
- 因为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线程。所以适合任务数比较密集,但每个任务执行时间较短的情况。
(3) 测试
@Slf4j(topic = "c.TestSynchronousQueue")
public class TestSynchronousQueue {
public static void main(String[] args) {
SynchronousQueue<Integer> integers = new SynchronousQueue<>();
new Thread(() -> {
try {
log.debug("putting {} ", 1);
integers.put(1);
log.debug("{} putted...", 1);
log.debug("putting...{} ", 2);
integers.put(2);
log.debug("{} putted...", 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
sleep(1);
new Thread(() -> {
try {
log.debug("taking {}", 1);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
sleep(1);
new Thread(() -> {
try {
log.debug("taking {}", 2);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3").start();
}
}
21:31:51.873 c.TestSynchronousQueue [t1] - putting 1
21:31:52.876 c.TestSynchronousQueue [t2] - taking 1
21:31:52.876 c.TestSynchronousQueue [t1] - 1 putted...
21:31:52.877 c.TestSynchronousQueue [t1] - putting...2
21:31:53.876 c.TestSynchronousQueue [t3] - taking 2
21:31:53.877 c.TestSynchronousQueue [t1] - 2 putted...
t1生成任务后,t2立马知道有任务,没有任务就阻塞住,因为队列没有容量,只是充当桥梁
3. newSingleThreadExecutor
(1) 描述
- 创建一个 Executor,它使用单个工作线程在无界队列中运行。
(但是请注意,如果这个单线程在关闭之前的执行过程中由于失败而终止,如果需要执行后续任务,一个新线程将取而代之。)
自己创建一个线程 和 线程池创建一个固定线程 的区别:
- 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,剩余的任务将不会执行
- 而线程池还会新建一个线程,保证线程池的正常工作
newSingleThreadExecutor 与 newFixedThreadPool 区别:
- Executors.newSingleThreadExecutor()线程个数始终为1,不能修改,可以保证任务按顺序执行。
- FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法。
- Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改。
- 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改。
源码:
public static ExecutorService newSingleThreadExecutor() {
// FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,
// ThreadPoolExecutor被包装在FinalizableDelegatedExecutorService 中,因此不能调用 ThreadPoolExecutor 中特有的方法
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(2) 特点
- 线程数固定为 1,任务数多于 1 时,会放入LinkedBlockingQueue无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
- 适合多个任务排队执行。
- 注意:LinkedBlockingQueue不指定容量,可能会造成OOM内存溢出。
(3) 测试
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(() -> {
log.debug("1");
int i = 1 / 0;
});
pool.execute(() -> {
log.debug("2");
});
pool.execute(() -> {
log.debug("3");
});
}
22:08:40.799 c.TestExecutors [pool-1-thread-1] - 1
22:08:40.803 c.TestExecutors [pool-1-thread-2] - 2
22:08:40.804 c.TestExecutors [pool-1-thread-2] - 3
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestExecutors.lambda$test2$0(TestExecutors.java:19)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
虽然线程1出现异常结束了,但是线程池创建了一个新线程将剩余任务执行完了
4. newScheduledThreadPool
(1) 描述
- 创建一个固定长度线程池,支持延迟及周期性任务执行,属于任务调度线程池
源码:
ScheduledThreadPoolExecutor 继承了 ThreadPoolExecutor 类,实现了 ScheduledExecutorService接口。
底层实际还是通过ThreadPoolExecutor创建线程池;
可以实现ScheduledExecutorService中schedule、scheduleAtFixedRate、scheduleWithFixedDelay方法,进行延迟及周期性任务执行;
核心线程数是手动设置的,
最大线程数是无限的(Integer.MAX_VALUE相当于无限),
生存时间0表示不释放临时线程,
使用的是DelayedWorkQueue延迟队列;
// 只用传入核心线程数
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
(2) 特点
- 线程数固定,任务数多于线程数时,会放入DelayedWorkQueue延迟队列排队
- 任务执行完毕,这些线程也不会被释放,用来执行延迟或周期性的任务
(3) newScheduledThreadPool 与 Timer 对比
Timer:
- 在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用
- 但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个
任务的延迟或异常都将会影响到之后的任务
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
log.debug("task 1");
sleep(2);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task 2");
}
};
log.debug("start...");
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
newScheduledThreadPool 延时执行任务:
- 存在多个线程时,同一时间可以有多个任务‘’
- 前一个任务的延迟或异常不会影响到之后的任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.schedule(() -> {
log.debug("task1");
// 出现异常也不会影响其他线程
int i = 1 / 0;
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
log.debug("task2");
}, 1, TimeUnit.SECONDS);
}
5. scheduleAtFixedRate
(1) 描述
- 创建并执行一个周期性动作,在给定的初始延迟后首先启用,然后在给定的时间段内启用,即执行将在 initialDelay 之后开始,然后是 initialDelay+period,然后是 initialDelay + 2 period,依此类推
源码:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
(2) 特点
- 如果任务的任何执行遇到异常,则后续执行将被抑制。否则,任务只会通过取消或终止执行程序而终止
- 如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会并发执行 (即执行时间超过间隔时间时,间隔时间会延长)
例如:间隔时间设置为1s,但执行时间花了2S,所以间隔时间会多等1s再执行下一个任务,后续就是每隔2s执行一次
(3) 测试
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.info("start...");
pool.scheduleAtFixedRate(() -> {
log.info("running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1, TimeUnit.SECONDS);
}
23:39:41.884 c.TestTimer [main] - start...
23:39:42.948 c.TestTimer [pool-1-thread-1] - running...
23:39:44.949 c.TestTimer [pool-1-thread-1] - running...
23:39:46.949 c.TestTimer [pool-1-thread-1] - running...
23:39:48.949 c.TestTimer [pool-1-thread-1] - running...
。。。。
输出结果分析:一开始,延时 1s,接下来,由于任务执行时间(2s) 大于 间隔时间(1S),间隔被『撑』到了 2s
(4) 定时任务案例
- 让每周三 10:00:00 定时执行任务
public static void main(String[] args) {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
// 获取周三时间
LocalDateTime time = now.withHour(10).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.WEDNESDAY);
// 如果 当前时间 > 本周周三,必须找到下周周三
if(now.compareTo(time) > 0) {
time = time.plusWeeks(1);
}
System.out.println(time);
// initailDelay 代表当前时间和周三的时间差
// period 一周的间隔时间
long initailDelay = Duration.between(now, time).toMillis();
long period = 1000 * 60 * 60 * 24 * 7;
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.scheduleAtFixedRate(() -> {
System.out.println("running...");
}, initailDelay, period, TimeUnit.MILLISECONDS);
}
2022-06-16T10:00:20.100 // 上周四,超过了周三
2022-06-22T10:00 // 所以从下周三开始
running...
running...
running...
running...
。。。。
6. scheduleWithFixedDelay
(1) 描述
- 创建并执行一个周期性动作,该动作首先在给定的初始延迟后启用,等待 上一个任务执行花费时间 + 间隔时间,之后再执行下一个任务
源码:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
(2) 特点
- 如果任务的任何执行遇到异常,则后续执行将被抑制。否则,任务只会通过取消或终止执行程序而终止
- 当上一个任务执行完后,下一个任务会再等一个间隔时间执行
例如:间隔时间设置为1s,但执行时间花了2S,所以下一个任务会在第3s后执行,后续就是每隔3s执行一次
(3) 测试
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.info("start...");
pool.scheduleWithFixedDelay(() -> {
log.info("running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1, TimeUnit.SECONDS);
}
23:47:45.050 c.TestTimer [main] - start...
23:47:46.138 c.TestTimer [pool-1-thread-1] - running...
23:47:49.140 c.TestTimer [pool-1-thread-1] - running...
23:47:52.141 c.TestTimer [pool-1-thread-1] - running...
23:47:55.142 c.TestTimer [pool-1-thread-1] - running...
。。。。
输出结果分析:一开始,延时 1s,上一个任务执行完花了2S,所以间隔都是 3s
三、提交任务的方法
1. execute
// 执行任务
void execute(Runnable command);
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ":执行任务");
});
}
2. submit
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<String> future = pool.submit(() -> {
log.debug("running");
Thread.sleep(1000);
return "ok";
});
log.debug("{}", future.get());
}
3. invokeAll
// 提交 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;
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
List<Future<String>> futures = pool.invokeAll(Arrays.asList(
() -> {
log.debug("begin");
Thread.sleep(1000);
return "1";
},
() -> {
log.debug("begin");
Thread.sleep(500);
return "2";
},
() -> {
log.debug("begin");
Thread.sleep(2000);
return "3";
}
));
futures.forEach( f -> {
try {
log.debug("{}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
同时提交3个任务后,有2个任务先被2个线程执行,最快的任务500ms执行完,再执行第三个任务,
最后所有任务执行完花了2s(取执行任务最长时间),同时打印结果
4. invokeAny
// 提交 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;
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
String result = pool.invokeAny(Arrays.asList(
() -> {
log.debug("begin 1");
Thread.sleep(1000);
log.debug("end 1");
return "1";
},
() -> {
log.debug("begin 2");
Thread.sleep(500);
log.debug("end 2");
return "2";
},
() -> {
log.debug("begin 3");
Thread.sleep(2000);
log.debug("end 3");
return "3";
}
));
log.debug("{}", result);
}
同时提交3个任务,有2个任务先被2个线程执行,最快的任务500ms执行完,返回此任务执行结果,其它任务不再执行
四、关闭线程池的方法
1. shutdown
- 线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行(例如:main线程调了shutdown方法后,main线程不会被阻塞)
源码:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(SHUTDOWN);
// 仅会打断空闲线程
interruptIdleWorkers();
onShutdown(); // 扩展点 hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
tryTerminate();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> result1 = pool.submit(() -> {
log.debug("task 1 running...");
Thread.sleep(1000);
log.debug("task 1 finish...");
return 1;
});
Future<Integer> result2 = pool.submit(() -> {
log.debug("task 2 running...");
Thread.sleep(1000);
log.debug("task 2 finish...");
return 2;
});
Future<Integer> result3 = pool.submit(() -> {
log.debug("task 3 running...");
Thread.sleep(1000);
log.debug("task 3 finish...");
return 3;
});
log.debug("shutdown");
pool.shutdown();
log.debug("main 线程继续执行");
pool.submit(() -> {
log.debug("task 4 running...");
Thread.sleep(1000);
log.debug("task 4 finish...");
return 4;
});
}
23:12:15.692 c.TestShutDown [main] - shutdown
23:12:15.692 c.TestShutDown [pool-1-thread-2] - task 2 running...
23:12:15.692 c.TestShutDown [pool-1-thread-1] - task 1 running...
23:12:15.696 c.TestShutDown [main] - main 线程继续执行
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@11531931 rejected from java.util.concurrent.ThreadPoolExecutor@5e025e70[Shutting down, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
at cn.itcast.n8.TestShutDown.main(TestShutDown.java:43)
23:12:16.697 c.TestShutDown [pool-1-thread-1] - task 1 finish...
23:12:16.697 c.TestShutDown [pool-1-thread-1] - task 3 running...
23:12:16.698 c.TestShutDown [pool-1-thread-2] - task 2 finish...
23:12:17.699 c.TestShutDown [pool-1-thread-1] - task 3 finish...
- 调用shutdown后,正在执行的线程、已经提交到任务队列的线程都不会终止,直到执行结束,线程池才关闭
- 调用shutdown后,不会阻塞调用线程的执行
- 调用shutdown后,不能再添加任务,否则报错
2. shutdownNow
- 线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
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;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> result1 = pool.submit(() -> {
log.debug("task 1 running...");
Thread.sleep(1000);
log.debug("task 1 finish...");
return 1;
});
Future<Integer> result2 = pool.submit(() -> {
log.debug("task 2 running...");
Thread.sleep(1000);
log.debug("task 2 finish...");
return 2;
});
Future<Integer> result3 = pool.submit(() -> {
log.debug("task 3 running...");
Thread.sleep(1000);
log.debug("task 3 finish...");
return 3;
});
log.debug("shutdownNow");
List<Runnable> runnables = pool.shutdownNow();
log.debug("返回队列中还没执行的任务:{}", runnables);
}
23:25:54.085 c.TestShutDown [main] - shutdownNow
23:25:54.085 c.TestShutDown [pool-1-thread-2] - task 2 running...
23:25:54.085 c.TestShutDown [pool-1-thread-1] - task 1 running...
23:25:54.088 c.TestShutDown [main] - 返回队列中还没执行的任务:[java.util.concurrent.FutureTask@47f37ef1]
3. 其他方法
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED 终止状态
boolean isTerminated();
// 调用 shutdown 后,由于调用线程(例如:mian线程)并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待(main线程调用shutdown后, 再调用awaitTermination,main线程就会等待)
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> result1 = pool.submit(() -> {
log.debug("task 1 running...");
Thread.sleep(1000);
log.debug("task 1 finish...");
return 1;
});
Future<Integer> result2 = pool.submit(() -> {
log.debug("task 2 running...");
Thread.sleep(1000);
log.debug("task 2 finish...");
return 2;
});
Future<Integer> result3 = pool.submit(() -> {
log.debug("task 3 running...");
Thread.sleep(1000);
log.debug("task 3 finish...");
return 3;
});
log.debug("shutdown");
pool.shutdown();
log.debug("main 线程等待所有任务终止后再执行");
pool.awaitTermination(3, TimeUnit.SECONDS); // 最多等3s
log.debug("main 线程等完后继续执行");
}
23:15:25.578 c.TestShutDown [main] - shutdown
23:15:25.581 c.TestShutDown [main] - main 线程等待所有任务终止后再执行
23:15:25.578 c.TestShutDown [pool-1-thread-2] - task 2 running...
23:15:25.578 c.TestShutDown [pool-1-thread-1] - task 1 running...
23:15:26.582 c.TestShutDown [pool-1-thread-1] - task 1 finish...
23:15:26.582 c.TestShutDown [pool-1-thread-2] - task 2 finish...
23:15:26.582 c.TestShutDown [pool-1-thread-2] - task 3 running...
23:15:27.582 c.TestShutDown [pool-1-thread-2] - task 3 finish...
23:15:27.582 c.TestShutDown [main] - main 线程等完后继续执行
所有任务执行完没有超过3s,所以awaitTermination可以提前等待结束
五、 多线程设计模式之工作线程模式
1. 定义
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
- 例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那么成本就太高了
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
- 例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不高,分成服务员(线程池A)与厨师(线程池B)更为合理
2. 饥饿现象
固定大小线程池会有饥饿现象
例如:
- 两个工人是同一个线程池中的两个线程
- 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
- 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
- 后厨做菜:没啥说的,做就是了
- 比如工人A 处理了点餐任务,接下来它要等着 工人B 把菜做好,然后上菜,他俩也配合的蛮好
- 但现在同时来了两个客人,这个时候工人A 和工人B 都去处理点餐了,这时没人做饭了,线程数不够出现饥饿现象
案例:
- (1)1个线程池,2个核心线程,做2个任务
@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
// pool
pool.execute(() -> {
log.debug("处理点餐...");
// pool
Future<String> f = pool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
// 等待前一个做菜任务
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
1个线程池,2个核心线程,做2个任务,可以成功做完
- (2)1个线程池,2个核心线程,做4个任务
Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.debug("处理点餐...");
Future<String> f = pool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
pool.execute(() -> {
log.debug("处理点餐...");
Future<String> f = pool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
1个线程池,2个核心线程,做4个任务,出现了饥饿现象,由于“上菜"需要等到"做菜"完成,
所以2个线程都在等待时,没有足够的线程去“做菜”,导致不能成功执行完
解决饥饿:
- 方法1:可以增加线程池的大小,不过不是根本解决方案
- 方法2:根据不同的任务类型,采用不同的线程池
@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
// 处理点餐和上菜的线程池
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
// 处理做菜的线程池
ExecutorService cookPool = Executors.newFixedThreadPool(1);
// waiterPool
waiterPool.execute(() -> {
log.debug("处理点餐...");
// cookPool
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
// waiterPool
waiterPool.execute(() -> {
log.debug("处理点餐...");
// cookPool
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
六、配置线程池大小
- 过小会导致程序不能充分地利用系统资源、容易导致饥饿
- 过大会导致更多的线程上下文切换,占用更多内存
1. CPU 密集型运算
- 对于计算密集型的应用,完全是靠CPU的核数来工作,所以为了让它的优势完全发挥出来,避免过多的线程上下文切换,比较理想方案是:
- 通常采用 线程数 = CPU核数+1(也可以设置成 CPU核数*2) 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费
2. I/O 密集型运算
- CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程 RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,线程就会处于等待状态,当IO结束,数据准备好后,线程才会继续执行
- 对于IO密集型的应用,可以多设置一些线程池中线程的数量,这样就能让在等待IO的这段时间内,线程可以去做其它事,提高并发处理效率
经验公式如下:
公式一:线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间
例如: 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式
4 * 100% * 100% / 50% = 8
例如: 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 cpu 被 100% 利用,套用公式
4 * 100% * 100% / 10% = 40
公式二:线程数 = CPU核心数/(1-阻塞系数) 这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。
套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整:
final int poolSize = (int)(cpuCore/(1-0.9))
针对于阻塞系数,《Programming Concurrency on the JVM Mastering》即《Java 虚拟机并发编程》中有提到一句话:
对于阻塞系数,我们可以先试着猜测,抑或采用一些细嫩分析工具或java.lang.management API
来确定线程花在系统/IO操作上的时间与CPU密集任务所耗的时间比值。
七、处理线程执行任务时的异常
1. 主动捕获异常
- try-catch 主动捕获异常
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
try {
log.debug("task1");
int i = 1 / 0;
} catch (Exception e) {
log.error("error:", e);
}
});
}
23:59:10.093 c.TestTimer [pool-1-thread-1] - task1
23:59:10.100 c.TestTimer [pool-1-thread-1] - error:
java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
2. 使用 Future 返回异常
- 提交Callable 任务,配合 Future 可以获取返回值
- 没有异常时,返回的是正常值
- 有异常时,返回的是异常信息
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
log.debug("task1");
int i = 1 / 0;
return true;
});
log.debug("result:{}", f.get());
}
00:00:50.799 c.TestTimer [pool-1-thread-1] - task1
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at cn.itcast.n8.TestTimer.main(TestTimer.java:40)
Caused by: java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:37)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)