8.2 ThreadPoolExecutor
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
8.2.1 线程池状态
【草稿纸】- 不严谨
回忆自定义线程池
线程集合(消费者)、任务队列、生产者
消费者从任务队列中获取 task 并执行
生产者向任务队列中添加 task
RUNNING:正常
SHUTDOWN:禁止生产者向任务队列中添加 task
STOP:禁止消费者执行 task
TIDYING:无 task 需被消费者执行
TERMINATED:进程结束
通过比较线程池状态的高 3 位得出:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
高 1 位为符号位:0 - 整数,1 - 负数
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程数量合二为一,这样就可以用一次 cas 原子操作进行赋值
// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
8.2.2 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1. corePoolSize:要保留在池中的线程数,即使它们处于空闲状态,除非设置了{@code allowCoreThreadTimeOut}
2. maximumPoolSize:池中允许的最大线程数
3. keepAliveTime:当线程数大于核心时,这是多余空闲线程在终止前等待新任务的最长时间。
4. unit:{@code keepAliveTime}参数的时间单位
5. workQueue:在执行任务之前用于保存任务的队列。此队列将只包含由{@code execute}方法提交的{@codeRunnable}任务。
6. threadFactory:执行器创建新线程时要使用的工厂
7. handler:当由于达到线程边界和队列容量而阻止执行时要使用的处理程序
【草稿纸】- 不严谨
线程集合中有核心线程、救急线程
当 corePoolSize = 2; maximumPoolSize = 3; workQueue.size() = 0; 时
线程的创建方式为懒惰初始化,故而线程池初期时是没有线程的
生产者向线程池提交 task 后,[核心]线程才被创建出来用于执行这个 task
当[核心]线程数等于 corePoolSize 时,task 会被添加到 workQueue 中
若 workQueue 为有界队列,当 workQueue 中的 task 数等于 workQueue.size() 时,仍有 task 被提交,则创建[救急]线程用于执行这个 task
若创建 maximumPoolSize - corePoolSize 个救急线程后,仍有 task 被提交,则启用拒绝策略(handler)
核心线程不再执行 task 后,不会释放
救急线程不再执行 task keepAliveTime unit 后,被释放
corePoolSize:核心线程数
maximumPoolSize:允许的最大线程数
keepAliveTime:救急线程不再执行 task 后存活的时间
unit:keepAliveTime 的单位
workQueue:任务队列
threadFactory:给线程起名?存疑
handler:拒绝策略
8.2.3 newFixedThreadPool
newFixedThreadPool(int nThreads)
// class Executors
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
1. 因为核心线程数与最大线程数均为 nThreads, 所以救急线程数为 0
2. 因为救急线程数为 0,所以救急线程存活时间为 0
3. 因为 new LinkedBlockingQueue<Runnable>(),所以任务队列的容量为 Integer.MAX_VALUE
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
【举个栗子】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(() -> {
log.debug("1");
});
service.execute(() -> {
log.debug("2");
});
service.execute(() -> {
log.debug("3");
});
}
}
// 某次运行结果
09:54:40 [pool-1-thread-2] c.Test1 - 2
09:54:40 [pool-1-thread-1] c.Test1 - 1
09:54:40 [pool-1-thread-2] c.Test1 - 3
newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
// class ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
// class Executors
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
8.2.4 newCachedThreadPool
// class Executors
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
1. 因为核心线程数为 0 且最大线程数为 Integer.MAX_VALUE,所以救急线程数为 Integer.MAX_VALUE
2. 救急线程执行完 task 后的存活时间为 60s
3. 【个人理解】SynchronousQueue
任务队列的容量为 0
生产者向线程池中提交任务选择无限制等待方式
唯有线程池中有空闲线程时,生产者向线程池中提交任务才不会被阻塞
【来吧,模拟展示】 SynchronousQueue
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.SynchronousQueue;
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
new Thread(() ->{
try {
log.debug("t1-1. putting...");
queue.put(1);
log.debug("t1-1. put...");
log.debug("t1-2. putting...");
queue.put(2);
log.debug("t1-2. put...");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
Thread.sleep(1000);
new Thread(() ->{
try {
log.debug("t2. taking...");
queue.take();
log.debug("t2. toke...");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
Thread.sleep(1000);
new Thread(() ->{
try {
log.debug("t3. taking...");
queue.take();
log.debug("t3. toke...");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3").start();
}
}
// 某次运行结果
11:11:42 [t1] c.Test2 - t1-1. putting...
11:11:43 [t2] c.Test2 - t2. taking...
11:11:43 [t2] c.Test2 - t2. toke...
11:11:43 [t1] c.Test2 - t1-1. put...
11:11:43 [t1] c.Test2 - t1-2. putting...
11:11:44 [t3] c.Test2 - t3. taking...
11:11:44 [t3] c.Test2 - t3. toke...
11:11:44 [t1] c.Test2 - t1-2. put...
进程已结束,退出代码 0
8.2.5 newSingleThreadExecutor
// class Executors
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
1. 因为核心线程数为 1 且最大线程数为 1,所以救急线程数为 0
2. 因为救急线程数为 0,所以救急线程存活时间为 0
3. 因为 new LinkedBlockingQueue<Runnable>(),所以任务队列的容量为 Integer.MAX_VALUE
【抛出问题】
当 newFixedThreadPool(int nThreads) 中 nThreads 为 1 时,newFixedThreadPool 与 newSingleThreadExecutor 有什么区别?
区别一:newFixedThreadPool 中核心线程数可被修改;newSingleThreadExecutor 中核心线程数固定为 1,不允许被修改
区别二:newFixedThreadPool 中直接返回 new ThreadPoolExecutor;newSingleThreadExecutor 中返回由 new FinalizableDelegatedExecutorService 包装后的 new ThreadPoolExecutor
8.2.6 提交任务
1. execute 和 submit
// 无返回值
void execute(Runnable command)
// 有返回值
<T> Future<T> submit(Callable<T> task)
【举个栗子】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<String> result = pool.submit(() -> {
log.debug("task begin...");
Thread.sleep(1000);
log.debug("task end...");
return "task result...";
});
log.debug("result:{}", result.get());
}
}
// 某次运行结果
16:50:37 [pool-1-thread-1] c.Test3 - task begin...
16:50:38 [pool-1-thread-1] c.Test3 - task end...
16:50:38 [main] c.Test3 - result:task result...
2. invokeAll
执行并返回所有被提交给线程池的 task
// 无限时
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
// 超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
【举个栗子 - invokeAll[无限时]】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
List<Future<String>> results = pool.invokeAll(Arrays.asList(
() -> {
log.debug("task1 begin...");
Thread.sleep(1000);
log.debug("task1 end...");
return "task1 result...";
},
() -> {
log.debug("task2 begin...");
Thread.sleep(500);
log.debug("task2 end...");
return "task2 result...";
},
() -> {
log.debug("task3 begin...");
Thread.sleep(2000);
log.debug("task3 end...");
return "task3 result...";
}
)
);
results.forEach(f -> {
try {
log.debug("result:{}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
// 某次运行结果
17:10:04 [pool-1-thread-1] c.Test4 - task1 begin...
17:10:04 [pool-1-thread-2] c.Test4 - task2 begin...
17:10:04 [pool-1-thread-2] c.Test4 - task2 end...
17:10:04 [pool-1-thread-2] c.Test4 - task3 begin...
17:10:05 [pool-1-thread-1] c.Test4 - task1 end...
17:10:06 [pool-1-thread-2] c.Test4 - task3 end...
17:10:06 [main] c.Test4 - result:task1 result...
17:10:06 [main] c.Test4 - result:task2 result...
17:10:06 [main] c.Test4 - result:task3 result...
3. invokeAny
返回第一个被执行完的 task 的执行结果,并打断线程池中正在执行 task 的线程,且不再获取并执行任务队列中的 task
// 无限时
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
// 超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
【举个栗子 - invokeAny[无限时]】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j(topic = "c.Test5")
public class Test5 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(2);
String result = pool.invokeAny(Arrays.asList(
() -> {
log.debug("task1 begin...");
Thread.sleep(1000);
log.debug("task1 end...");
return "task1 result...";
},
() -> {
log.debug("task2 begin...");
Thread.sleep(500);
log.debug("task2 end...");
return "task2 result...";
},
() -> {
log.debug("task3 begin...");
Thread.sleep(2000);
log.debug("task3 end...");
return "task3 result...";
},
() -> {
log.debug("task4 begin...");
Thread.sleep(1500);
log.debug("task4 end...");
return "task4 result...";
}
));
log.debug("result:{}", result);
}
}
// 某次运行结果
17:19:25 [pool-1-thread-1] c.Test5 - task1 begin...
17:19:25 [pool-1-thread-2] c.Test5 - task2 begin...
17:19:26 [pool-1-thread-2] c.Test5 - task2 end...
17:19:26 [pool-1-thread-2] c.Test5 - task3 begin...
17:19:26 [main] c.Test5 - result:task2 result...
8.2.7 线程池状态
1. void shutdown()
修改线程池状态为 SHUTDOWN
该状态下,
执行已提交线程池的任务,线程池不再接受新的任务
生产者可继续运行,无需等待线程池执行完已提交的任务
【举个栗子】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<String> result1 = pool.submit(() -> {
log.debug("task1 begin...");
Thread.sleep(1000);
log.debug("task1 end...");
return "task1 result...";
});
Future<String> result2 = pool.submit(() -> {
log.debug("task2 begin...");
Thread.sleep(1000);
log.debug("task2 end...");
return "task2 result...";
});
Future<String> result3 = pool.submit(() -> {
log.debug("task3 begin...");
Thread.sleep(1000);
log.debug("task3 end...");
return "task3 result...";
});
log.debug("shutdown...");
pool.shutdown();
log.debug("other...");
Future<String> result4 = pool.submit(() -> {
log.debug("task4 begin...");
Thread.sleep(1000);
log.debug("task4 end...");
return "task4 result...";
});
}
}
// 某次运行结果
18:00:06 [main] c.Test6 - shutdown...
18:00:06 [pool-1-thread-2] c.Test6 - task2 begin...
18:00:06 [pool-1-thread-1] c.Test6 - task1 begin...
18:00:06 [main] c.Test6 - other...
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@256216b3 rejected from java.util.concurrent.ThreadPoolExecutor@2a18f23c[Shutting down, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
at com.rui.eight.Test6.main(Test6.java:39)
18:00:07 [pool-1-thread-1] c.Test6 - task1 end...
18:00:07 [pool-1-thread-2] c.Test6 - task2 end...
18:00:07 [pool-1-thread-1] c.Test6 - task3 begin...
18:00:08 [pool-1-thread-1] c.Test6 - task3 end...
进程已结束,退出代码 1
若生产者希望等待,可通过 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
【举个栗子】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<String> result1 = pool.submit(() -> {
log.debug("task1 begin...");
Thread.sleep(1000);
log.debug("task1 end...");
return "task1 result...";
});
Future<String> result2 = pool.submit(() -> {
log.debug("task2 begin...");
Thread.sleep(1000);
log.debug("task2 end...");
return "task2 result...";
});
Future<String> result3 = pool.submit(() -> {
log.debug("task3 begin...");
Thread.sleep(1000);
log.debug("task3 end...");
return "task3 result...";
});
log.debug("shutdown...");
pool.shutdown();
pool.awaitTermination(3, TimeUnit.SECONDS);
log.debug("other...");
}
}
// 某次运行结果
18:08:48 [main] c.Test6 - shutdown...
18:08:48 [pool-1-thread-2] c.Test6 - task2 begin...
18:08:48 [pool-1-thread-1] c.Test6 - task1 begin...
18:08:49 [pool-1-thread-2] c.Test6 - task2 end...
18:08:49 [pool-1-thread-1] c.Test6 - task1 end...
18:08:49 [pool-1-thread-1] c.Test6 - task3 begin...
18:08:50 [pool-1-thread-1] c.Test6 - task3 end...
18:08:50 [main] c.Test6 - other...
进程已结束,退出代码 0
2. List<Runnable> shutdownNow()
修改线程池状态为 STOP
该状态下,线程池中不再执行 task
【举个栗子】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<String> result1 = pool.submit(() -> {
log.debug("task1 begin...");
Thread.sleep(1000);
log.debug("task1 end...");
return "task1 result...";
});
Future<String> result2 = pool.submit(() -> {
log.debug("task2 begin...");
Thread.sleep(1000);
log.debug("task2 end...");
return "task2 result...";
});
Future<String> result3 = pool.submit(() -> {
log.debug("task3 begin...");
Thread.sleep(1000);
log.debug("task3 end...");
return "task3 result...";
});
log.debug("shutdownNow...");
pool.shutdownNow();
log.debug("other...");
}
}
// 某次运行结果
18:02:37 [main] c.Test6 - shutdownNow...
18:02:37 [pool-1-thread-2] c.Test6 - task2 begin...
18:02:37 [pool-1-thread-1] c.Test6 - task1 begin...
18:02:37 [main] c.Test6 - other...
进程已结束,退出代码 0
3. boolean isShutdown()
若线程池状态为 RUNNABLE,返回 true;反之,返回 false
4. boolean isTerminated()
若线程池状态为 TERMINATED,返回 true;反之,返回 false
8.2.8 Worker Thread 模式
假设,有一家餐馆,将线程看做是员工,将任务看做是顾客
餐馆刚刚开业,招聘了 2 名员工,但并未对他们安排特定的分工(啥都干)
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test7")
public class Test7 {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static String cooking() {
return MENU.get(new Random().nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.debug("接单...");
Future<String> result = pool.submit(() -> {
log.debug("烹饪...");
return cooking();
});
try {
log.debug("出餐:{}", result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
// 某次运行结果
18:44:21 [pool-1-thread-1] c.Test7 - 接单...
18:44:21 [pool-1-thread-2] c.Test7 - 烹饪...
18:44:21 [pool-1-thread-1] c.Test7 - 出餐:地三鲜
顾客增多...
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test7")
public class Test7 {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static String cooking() {
return MENU.get(new Random().nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.debug("接单...");
Future<String> result = pool.submit(() -> {
log.debug("烹饪...");
return cooking();
});
try {
log.debug("出餐:{}", result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
pool.execute(() -> {
log.debug("接单...");
Future<String> result = pool.submit(() -> {
log.debug("烹饪...");
return cooking();
});
try {
log.debug("出餐:{}", result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
// 某次运行结果
18:46:38 [pool-1-thread-2] c.Test7 - 接单...
18:46:38 [pool-1-thread-1] c.Test7 - 接单...
问题暴露 - 饥饿
员工 A 提议:招工
治标不治本
员工 B 提议:分工
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test7")
public class Test7 {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static String cooking() {
return MENU.get(new Random().nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
ExecutorService chefPool = Executors.newFixedThreadPool(1);
waiterPool.execute(() -> {
log.debug("接单...");
Future<String> result = chefPool.submit(() -> {
log.debug("烹饪...");
return cooking();
});
try {
log.debug("出餐:{}", result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.debug("接单...");
Future<String> result = chefPool.submit(() -> {
log.debug("烹饪...");
return cooking();
});
try {
log.debug("出餐:{}", result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
// 某次运行结果
18:51:13 [pool-1-thread-1] c.Test7 - 接单...
18:51:13 [pool-2-thread-1] c.Test7 - 烹饪...
18:51:13 [pool-1-thread-1] c.Test7 - 出餐:辣子鸡丁
18:51:13 [pool-1-thread-1] c.Test7 - 接单...
18:51:13 [pool-2-thread-1] c.Test7 - 烹饪...
18:51:13 [pool-1-thread-1] c.Test7 - 出餐:宫保鸡丁
创建多少线程池合适
1. CPU 密集型运算
CPU core 数 + 1
2. I/O 密集型运算
CPU core 数 * 期望 CPU 利用率 * 总时间(CPU 计算时间 + 等待时间)/ CPU 计算时间
【举个栗子】
4 核 CPU 计算时间为 10%,等待时间为 90%,期望 CPU 利用率为 100%
4 * 100% * (10% + 90%)/ 10%
8.2.9 Timer
所有任务皆由同一个线程调度
因此,任务的执行为串行执行,若执行任务时出现延迟或异常,会影响之后任务的执行。
【示例】
『任务1』延时
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Timer;
import java.util.TimerTask;
@Slf4j(topic = "c.Test8")
public class Test8 {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
try {
log.debug("task1 begin...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task2 begin...");
}
};
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
}
// 某次运行结果
08:40:14 [Timer-0] c.Test8 - task1 begin...
08:40:16 [Timer-0] c.Test8 - task2 begin...
『任务1』异常
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.Timer;
import java.util.TimerTask;
@Slf4j(topic = "c.Test8")
public class Test8 {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
log.debug("task1 begin...");
int i = 1 / 0;
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task2 begin...");
}
};
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的异常,影响了『任务2』的执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
}
// 某次运行结果
08:42:35 [Timer-0] c.Test8 - task1 begin...
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
at com.rui.eight.Test8$1.run(Test8.java:16)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
进程已结束,退出代码 0
8.2.10 newScheduledThreadPool
所有任务可由不同线程调度
执行任务时出现异常,不会影响之后任务的执行。
【示例】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.schedule(() -> {
log.debug("task1 begin...");
int i = 1 / 0;
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
log.debug("task2 begin...");
}, 1, TimeUnit.SECONDS);
}
}
// 某次运行结果
09:04:27 [pool-1-thread-1] c.Test9 - task1 begin...
09:04:27 [pool-1-thread-1] c.Test9 - task2 begin...
scheduleAtFixedRate
周期性
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
Runnable command:要执行的任务
long initialDelay:延迟第一次执行的时间
long period:连续执行之间的时间
TimeUnit unit:long period 的单位
【示例】
如果此任务的任何执行时间都超过其周期,则后续执行可能会延迟开始,但不会同时执行。
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
try {
log.debug("running...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1, TimeUnit.SECONDS);
}
}
// 某次运行结果
09:15:23 [main] c.Test9 - start...
09:15:24 [pool-1-thread-1] c.Test9 - running...
09:15:26 [pool-1-thread-1] c.Test9 - running...
09:15:28 [pool-1-thread-1] c.Test9 - running...
09:15:30 [pool-1-thread-1] c.Test9 - running...
// ...
【应用】
需求:从下周五 9:59:30 开始每隔 1 秒执行一次任务
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
LocalDateTime now = LocalDateTime.now();
log.debug("now:{}", now);
LocalDateTime want = now.withHour(9).withMinute(59).withSecond(30).withNano(0).with(DayOfWeek.FRIDAY);
if (now.compareTo(want) > 0) {
want = want.plusWeeks(1);
}
long initialDelay = Duration.between(now, want).toMillis();
long period = 1000;
pool.scheduleAtFixedRate(() -> {
log.debug("running...");
}, initialDelay, period, TimeUnit.MILLISECONDS);
}
}
// 某次运行结果
09:59:14 [main] c.Test11 - now:2023-09-22T09:59:14.806
09:59:30 [pool-1-thread-1] c.Test11 - running...
09:59:31 [pool-1-thread-1] c.Test11 - running...
09:59:32 [pool-1-thread-1] c.Test11 - running...
09:59:33 [pool-1-thread-1] c.Test11 - running...
09:59:34 [pool-1-thread-1] c.Test11 - running...
09:59:35 [pool-1-thread-1] c.Test11 - running...
09:59:36 [pool-1-thread-1] c.Test11 - running...
09:59:37 [pool-1-thread-1] c.Test11 - running...
scheduleWithFixedDelay
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
Runnable command:要执行的任务
long initialDelay:延迟第一次执行的时间
long delay:一个执行终止到下一个执行开始之间的延迟
TimeUnit unit:long period 的单位
【示例】
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleWithFixedDelay(()->{
try {
log.debug("running...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},1,1,TimeUnit.SECONDS);
}
}
// 某次运行结果
09:17:48 [main] c.Test9 - start...
09:17:49 [pool-1-thread-1] c.Test9 - running...
09:17:52 [pool-1-thread-1] c.Test9 - running...
09:17:55 [pool-1-thread-1] c.Test9 - running...
09:17:58 [pool-1-thread-1] c.Test9 - running...
09:18:01 [pool-1-thread-1] c.Test9 - running...
8.2.11 正确处理执行任务异常
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
log.debug("task begin...");
int i = 1 / 0;
});
}
}
// 某次运行结果
09:29:54 [pool-1-thread-1] c.Test10 - task begin...
【抛出疑问】控制台未处理异常,如何正确处理执行任务异常?
方法一:try / catch
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
try {
log.debug("task begin...");
int i = 1 / 0;
} catch (Exception e) {
log.error("", e);
}
});
}
}
// 某次运行结果
09:33:38 [pool-1-thread-1] c.Test10 - task begin...
09:33:38 [pool-1-thread-1] c.Test10 -
java.lang.ArithmeticException: / by zero
at com.rui.eight.Test10.lambda$main$0(Test10.java:16)
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:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
方法二: Future
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> result = pool.submit(() -> {
log.debug("task begin...");
int i = 1 / 0;
return true;
});
log.debug("{}", result.get());
}
}
// 某次运行结果
09:36:28 [pool-1-thread-1] c.Test10 - task begin...
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 com.rui.eight.Test10.main(Test10.java:21)
Caused by: java.lang.ArithmeticException: / by zero
at com.rui.eight.Test10.lambda$main$0(Test10.java:17)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
8.3 fork / join
需求:计算 1 ~ n 的和
思路:递归、拆分
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new MyTask(5)));
}
}
@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask<Integer> {
private int n;
public MyTask(int n) {
this.n = n;
}
@Override
public String toString() {
return "(" + n + ")";
}
@Override
protected Integer compute() {
if (n == 1) {
log.debug("fork({})/join({}) = {}", n, n, n);
return n;
}
// 将任务进行拆分(fork)
MyTask t1 = new MyTask(n - 1);
t1.fork();
log.debug("{} + fork{}", n, t1);
// 合并(join)结果
int result = n + t1.join();
log.debug("{} + join{} = {}", n, t1, result);
return result;
}
}
// 某次运行结果
10:52:57 [ForkJoinPool-1-worker-1] c.MyTask - 5 + fork(4)
10:52:57 [ForkJoinPool-1-worker-0] c.MyTask - 2 + fork(1)
10:52:57 [ForkJoinPool-1-worker-2] c.MyTask - 4 + fork(3)
10:52:57 [ForkJoinPool-1-worker-3] c.MyTask - 3 + fork(2)
10:52:57 [ForkJoinPool-1-worker-0] c.MyTask - fork(1)/join(1) = 1
10:52:57 [ForkJoinPool-1-worker-0] c.MyTask - 2 + join(1) = 3
10:52:57 [ForkJoinPool-1-worker-3] c.MyTask - 3 + join(2) = 6
10:52:57 [ForkJoinPool-1-worker-2] c.MyTask - 4 + join(3) = 10
10:52:57 [ForkJoinPool-1-worker-1] c.MyTask - 5 + join(4) = 15
15
进程已结束,退出代码 0
优化思路:二分法、拆分
package com.rui.eight;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new MyTask(1, 5)));
}
}
@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask<Integer> {
private int begin;
private int end;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public String toString() {
return "(" + begin + "," + end + ")";
}
@Override
protected Integer compute() {
if (begin == end) {
log.debug("fork({},{})/join{{},{}} = {}", begin, end, begin, end, begin);
return begin;
}
if (begin + 1 == end) {
log.debug("fork({},{})/join{{},{}} = {}", begin, end, begin, end, begin + end);
return begin + end;
}
int mid = (begin + end) / 2;
MyTask t1 = new MyTask(begin, mid);
t1.fork();
MyTask t2 = new MyTask(mid + 1, end);
t2.fork();
log.debug("fork{} + fork{}", t1, t2);
int result = t1.join() + t2.join();
log.debug("join{} + join{} = {}", t1, t2, result);
return result;
}
}
// 某次运行结果
11:07:54 [ForkJoinPool-1-worker-3] c.MyTask - fork(4,5)/join{4,5} = 9
11:07:54 [ForkJoinPool-1-worker-0] c.MyTask - fork(1,2)/join{1,2} = 3
11:07:54 [ForkJoinPool-1-worker-1] c.MyTask - fork(1,3) + fork(4,5)
11:07:54 [ForkJoinPool-1-worker-2] c.MyTask - fork(1,2) + fork(3,3)
11:07:54 [ForkJoinPool-1-worker-3] c.MyTask - fork(3,3)/join{3,3} = 3
11:07:54 [ForkJoinPool-1-worker-2] c.MyTask - join(1,2) + join(3,3) = 6
11:07:54 [ForkJoinPool-1-worker-1] c.MyTask - join(1,3) + join(4,5) = 15
15
进程已结束,退出代码 0
说些废话
本篇文章为博主日常学习记录,故而会概率性地存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!