JUC并发编程系列文章
http://t.csdn.cn/UgzQi
前言
一、线程池
1、自定义线程池
代码示例
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.testThread27")
public class testThread27 {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(1,1000L,TimeUnit.MILLISECONDS,1,
(queue, runnable) -> {
/**
* 1、队列满了就死等
* queue.addT(runnable);
* 2、满了带超时等待,等待超时不再执行任务
* queue.addTime(runnable,500L,TimeUnit.MILLISECONDS);
* 3、让调用者放弃任务的执行,满了什么都不做,自然就放弃了
* log.debug("队列满了,放弃任务"+runnable);
* 4、让调用者抛出异常
* throw new RuntimeException("队列已经满了,不能执行任务了");
* 5、让调用者自己创建线程执行任务,不再从线程池获取
* runnable.run();
*/
runnable.run();
});
for (int i =1; i<=5; i++){
int j = i;
threadPool.execute(()->{
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
log.debug("{}",j);
});
}
}
}
//策略模式
@FunctionalInterface
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue,Runnable runnable);
}
@Slf4j(topic = "c.ThreadPool")
//线程池
class ThreadPool {
//任务队列
private BlockingQueue<Runnable> taskQueue;
//线程集合
private HashSet<Work> workers = new HashSet();
//线程核心数
private int coreSize;
//获取任务超时时间
private Long tomeOut;
private TimeUnit unit;
//拒绝策略
private RejectPolicy<Runnable> rejectPolicy;
//初始化线程池
public ThreadPool(int coreSize, Long tomeOut, TimeUnit unit,int queueCapacity,RejectPolicy rejectPolicy) {
this.coreSize = coreSize;
this.tomeOut = tomeOut;
this.unit = unit;
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.rejectPolicy = rejectPolicy;
}
//执行任务
public void execute(Runnable runnable){
//如果任务数coreSize 没有超过线程集合的长度 就交给处理任务,超过了放入队列
synchronized (workers){
if(workers.size() < coreSize){
Work work = new Work(runnable);
log.debug("新增工作线程="+work+"新增任务"+runnable);
//把工作线程放入集合
workers.add(work);
work.start();
}else {
log.debug("任务数超过线程集合,放入任务队列"+runnable);
// taskQueue.addT(runnable);
/**
* 添加策略模式,下方权限给不同的任务自己去实现不同的业务,具体的操作抽象成一个接口,
* 具体的实现由调用者传递过来,具体的实现不写在 ThreadPool 线程池里面
* 1、队列满了就死等
* 2、满了带超时等待
* 3、让调用者放弃任务的执行
* 4、让调用者抛出异常
* 5、让调用者自己创建线程执行任务,不再从线程池获取
*/
taskQueue.tryPut(rejectPolicy,runnable);
}
}
}
//工作线程
class Work extends Thread{
private Runnable runnable;
public Work(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void run() {
//当 runnable不为空就执行,为空去任务队列拿任务执行( 再赋值给 runnable 就不为空了,继续循环执行任务,不设置超时时间两个线程执行完所有任务还是会一直等待)
// while (runnable != null || (runnable = taskQueue.take()) != null) {
while (runnable != null || (runnable = taskQueue.pool(1000L,TimeUnit.MILLISECONDS)) != null) {
//有超时时间的会返回一个 null,条件不成立就会走//任务执行完后移除线程
// synchronized (workers){
try {
log.debug("执行任务"+runnable);
runnable.run();
//执行完让 runnable 等于 null
runnable = null;
} catch (Exception e) {
e.printStackTrace();
}
}
//任务执行完后移除线程
synchronized (workers){
log.debug("任务执行完了,删除线程"+this);
workers.remove(this);
}
}
}
}
//阻塞队列
@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T> {
//线程池的任务队列
private Deque<Runnable> queue = new ArrayDeque();
//锁,队列的头部和尾部需要添加同步锁
private ReentrantLock lock = new ReentrantLock();
//队列满的条件变量
private Condition fullCond = lock.newCondition();
//队列空的条件变量
private Condition emptyConf = lock.newCondition();
//队列容量
private int capacity;
public BlockingQueue() {
}
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
//带超时的阻塞获取,无需一直等待
public Runnable pool(Long timeOut, TimeUnit unit){
lock.lock();
try {
long nanos = unit.toNanos(timeOut);
//判断队列是否为空,为空就等待
while (queue.isEmpty()){
//如果第二次循环过来发现 nanos 等于 0 就是等待超时,没有等到被唤醒。
if(nanos <= 0){
return null;
}
try {
//emptyConf.awaitNanos( 返回上一次等待的剩余时长,比如本来需要等待 2秒,在 1秒时被唤醒,
// 这时还要再走一遍循环,而这时就不需要再等 2 秒了,只需要等 1秒就够了。
nanos = emptyConf.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Runnable t = queue.removeFirst();
fullCond.signal();
return t;
}finally {
lock.unlock();
}
}
//阻塞获取
public Runnable take(){
lock.lock();
try {
//判断队列是否为空,为空就等待
while (queue.isEmpty()){
try {
emptyConf.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Runnable t = queue.removeFirst();
fullCond.signal();
return t;
}finally {
lock.unlock();
}
}
//阻塞添加
public void addT(Runnable t){
lock.lock();
try {
//判断队列是否已经满了
while (queue.size() == capacity){
try {
log.debug("队列已经满了,等待加入任务队列.....");
fullCond.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(t);
emptyConf.signal();
}finally {
lock.unlock();
}
}
//带超时时间的阻塞添加
public boolean addTime(Runnable t,Long timeOut,TimeUnit unit){
lock.lock();
try {
//转换同一时间单位
long nanos = unit.toNanos(timeOut);
//判断队列是否已经满了
while (queue.size() == capacity){
try {
if(nanos <= 0){
//超时添加失败
return false;
}
nanos = fullCond.awaitNanos(nanos);
log.debug("队列已经满了,等待加入任务队列.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(t);
emptyConf.signal();
//添加成功
return true;
}finally {
lock.unlock();
}
}
//队列容量
public int getSize(){
lock.lock();
try {
return queue.size();
}finally {
lock.unlock();
}
}
//拒绝策略
public void tryPut(RejectPolicy<T> rejectPolicy, Runnable runnable) {
lock.lock();
try {
//判断队列是否已满,满了执行拒绝策略
if(queue.size() == capacity){
rejectPolicy.reject(this,runnable);
}else {
//没有满就加入队列
log.debug("加入任务队列"+runnable);
queue.addLast(runnable);
emptyConf.signal();
}
}finally {
lock.unlock();
}
}
}
2、ThreadPoolExecutor
1、线程池的状态
因为第一位是符号位,RUNNING 是负数,所以最小.
// 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; }
2、构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
线程池工作方式
● 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务,线程池中的线程都是懒惰创建了,只有用到了才会创建。
● 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
● 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
● 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
○ AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
○ CallerRunsPolicy 让调用者运行任务
○ DiscardPolicy 放弃本次任务
○ DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
○ Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
○ Netty 的实现,是创建一个新线程来执行任务
○ ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
○ PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
● 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。
根据这个构造方法,JDK Executors类中提供了众多工厂方法来创建各种用途的线程池.
3、newFixedThreadPool 创建固定大小的线程池
4、newCachedThreadPool 带缓冲的线程池
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();
输出
11:48:15.500 c.TestSynchronousQueue [t1] - putting 1
11:48:16.500 c.TestSynchronousQueue [t2] - taking 1
11:48:16.500 c.TestSynchronousQueue [t1] - 1 putted...
11:48:16.500 c.TestSynchronousQueue [t1] - putting...2
11:48:17.502 c.TestSynchronousQueue [t3] - taking 2
11:48:17.503 c.TestSynchronousQueue [t1] - 2 putted...
评价 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。
适合任务数比较密集,但每个任务执行时间较短的情况
5、newSingleThreadExecutor 单线程的线程池
6、提交任务
7、关闭线程池
// 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
tryTerminate();
如果线程池关闭以后还有运行的线程,线程执行完任务后会自动结束,会将已经添加到任务队列的任务执行完。shutdown执行后,线程和任务还是会执行,但是调用shutdown的线程并不会阻塞,而是直接执行后面的方法,这时如果想等待线程池中的线程将任务执行完,再往下继续运行,可以在调用shutdown的线程里面再次调用 awaitTimenation()方法等待池和任务队列的结束。
8、任务调度线程池
代码示例
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.*;
@Slf4j(topic = "c.TestStarvation")
public class TestStarvation {
public static void main(String[] args) {
//使用 ScheduledExecutorService 改写:线程池只有一个线程还是会串行执行,如果发生异常不会影响后面的线程任务,但发生异常的任务会处理失败
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
log.debug("start...");
//延时从上一个任务的结束时间开始算,再延时 delay 1 秒 的参数,延时3秒
executor.scheduleWithFixedDelay(()-> {
log.debug("running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1, TimeUnit.SECONDS);
}
//执行时间较长,就会影响到定时时间
public void scheduleAtFixedRate(ScheduledExecutorService executor){
log.debug("start...");
//定时反复执行任务
executor.scheduleAtFixedRate(() -> {
log.debug("running...");
//如果任务的执行时间较长,就会影响到定时时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 1, TimeUnit.SECONDS);
}
//使用 ScheduledExecutorService代替 timer 执行线程池任务调度
public void executor(ScheduledExecutorService executor){
//使用 ScheduledExecutorService 改写:线程池只有一个线程还是和timer一样会串行执行,
// 如果发生异常不会影响后面的线程任务,但发生异常的任务会处理失败
// 添加两个任务,希望它们都在 1s 后执行
executor.schedule(() -> {
System.out.println("任务1,执行时间:" + new Date());
int i = 1/0;
System.out.println("你好");
try { Thread.sleep(2000); } catch (InterruptedException e) { }
}, 1000, TimeUnit.MILLISECONDS);
executor.schedule(() -> {
System.out.println("任务2,执行时间:" + new Date());
}, 1000, TimeUnit.MILLISECONDS);
}
//使用 timer 实现线程池任务调度
public void timer(){
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@SneakyThrows
@Override
public void run() {
log.debug("task 1");
//发声异常任务,后面的任务就不会被调度了。直接废了
int i = 1/0;
Thread.sleep(2000);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task 2");
}
};
log.debug("start...");
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
// 甚至如果task1出异常停止后,task2都不会执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
}
8.2、定时任务
代码示例:每星期四18点执行定时任务
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class testThread28 {
public static void main(String[] args) {
// 获得当前时间
LocalDateTime now = LocalDateTime.now();
// 获取本周四 18:00:00.000
LocalDateTime thursday =
now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
// 如果当前时间已经超过 本周四 18:00:00.000, 那么找下周四 18:00:00.000
if(now.compareTo(thursday) >= 0) {
thursday = thursday.plusWeeks(1);
}
// 计算时间差,即延时执行时间
long initialDelay = Duration.between(now, thursday).toMillis();//当前时间到下一周四18点的毫秒值
// 计算间隔时间,即 1 周的毫秒值
long oneWeek = 7 * 24 * 3600 * 1000;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
System.out.println("开始时间:" + new Date());
executor.scheduleAtFixedRate(() -> {
System.out.println("执行时间:" + new Date());
}, initialDelay, oneWeek, TimeUnit.MILLISECONDS);
}
}
9、正确处理任务异常
方法1:主动捉异常
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
try {
log.debug("task1");
int i = 1 / 0;
} catch (Exception e) {
log.error("error:", e);
}
});
输出
21:59:04.558 c.TestTimer [pool-1-thread-1] - task1
21:59:04.562 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
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
log.debug("task1");
int i = 1 / 0;
return true;
});
log.debug("result:{}", f.get());
输出
21:54:58.208 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:31)
Caused by: java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28)
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)
10、TomCat线程池
扩展了 ThreadPoolExecutor
Tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同
● 如果总线程数达到 maximumPoolSize
○ 这时不会立刻抛 RejectedExecutionException 异常
○ 而是再次尝试将任务放入队列,如果还失败,才抛出 RejectedExecutionException 异常
源码 tomcat-7.0.42
public void execute(Runnable command, long timeout, TimeUnit unit) {
submittedCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue)super.getQueue();
try {
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.");
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
Thread.interrupted();
throw new RejectedExecutionException(x);
}
} else {
submittedCount.decrementAndGet();
throw rx;
}
}
}
TaskQueue.java
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if ( parent.isShutdown() )
throw new RejectedExecutionException(
"Executor not running, can't force a command into the queue"
);
return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task
is rejected
}
Connector 配置
Executor 线程配置
11、Fork/Join线程池
使用
提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下面定义了一个对 1~n 之间的整数求和的任务
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
public class testThread28 {
public static void main(String[] args) {
//然后提交给 ForkJoinPool 来执行
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new AddTask1(5)));
}
}
@Slf4j(topic = "c.AddTask")
class AddTask1 extends RecursiveTask<Integer> {
int n;
public AddTask1(int n) {
this.n = n;
}
@Override
public String toString() {
return "{" + n + '}';
}
@Override
protected Integer compute() {
// 如果 n 已经为 1,可以求得结果了
if (n == 1) {
log.debug("join() {}", n);
return n;
}
// 将任务进行拆分(fork)
AddTask1 t1 = new AddTask1(n - 1);
t1.fork();
log.debug("fork() {} + {}", n, t1);
// 合并(join)结果
int result = n + t1.join();
log.debug("join() {} + {} = {}", n, t1, result);
return result;
}
}
用图来表示
细节改进
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
public class testThread28 {
public static void main(String[] args) {
//然后提交给 ForkJoinPool 来执行
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new AddTask3(1, 10)));
/**
*
* 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - fork() {6,8} + {9,10} = ?
* 15:05:24 [ForkJoinPool-1-worker-2] c.AddTask - fork() {1,3} + {4,5} = ?
* 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - fork() {1,5} + {6,10} = ?
* 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - fork() {1,2} + {3,3} = ?
* 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - join() 4 + 5 = 9
* 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - fork() {6,7} + {8,8} = ?
* 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - join() 1 + 2 = 3
* 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() 6 + 7 = 13
* 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - join() 3
* 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() 8
* 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - join() {1,2} + {3,3} = 6
* 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() {6,7} + {8,8} = 21
* 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - join() 9 + 10 = 19
* 15:05:24 [ForkJoinPool-1-worker-2] c.AddTask - join() {1,3} + {4,5} = 15
* 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() {6,8} + {9,10} = 40
* 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - join() {1,5} + {6,10} = 55
* 55
*/
}
}
@Slf4j(topic = "c.AddTask")
class AddTask3 extends RecursiveTask<Integer> {
int begin;
int end;
public AddTask3(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public String toString() {
return "{" + begin + "," + end + '}';
}
@Override
protected Integer compute() {
// 5, 5
if (begin == end) {
log.debug("join() {}", begin);
return begin;
}
// 4, 5
if (end - begin == 1) {
log.debug("join() {} + {} = {}", begin, end, end + begin);
return end + begin;
}
// 1 5
int mid = (end + begin) / 2; // 3
AddTask3 t1 = new AddTask3(begin, mid); // 1,3
t1.fork();
AddTask3 t2 = new AddTask3(mid + 1, end); // 4,5
t2.fork();
log.debug("fork() {} + {} = ?", t1, t2);
int result = t1.join() + t2.join();
log.debug("join() {} + {} = {}", t1, t2, result);
return result;
}
}
用图来表示
异步模式:工作线程🔞
1、定义
2、饥饿现象:因线程数量的不足
固定大小的线程池和单线程的线程池都会有饥饿线程,带缓冲的线程池不会有饥饿现象
线程饥饿代码示例
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.TestStarvation")
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 executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> {
log.debug("处理点餐...");
Future<String> f = executorService.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
executorService.execute(() -> {
log.debug("处理点餐...");
Future<String> f = executorService.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
解决方法1:添加线程
本来两个线程变成3个线程
解决方法可以增加线程池的大小,不过不是根本解决方案,还是前面提到的,不同的任务类型,采用不同的线程
池。
ExecutorService executorService = Executors.newFixedThreadPool(3);
解决方法2:不同的任务类型,采用不同的线程池
代码示例
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.TestStarvation")
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.execute(() -> {
log.debug("处理点餐...");
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.debug("处理点餐...");
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
3、创建多少线程合适