模拟线程饥饿
思路和代码来自于网络,本文仅是个人记录性质,感谢原作者们。
思路
线程1、线程2来自于一个固定线程池newFixedThreadPool(2),然后分别都去执行任务A,任务A当中会向同一个线程池提交任务B返回Future,同时任务A使用Future.get()等待任务B的返回结果才能继续往下执行。
于是会发生以下现象:线程1、2都在执行任务A,都向线程池提交了任务B、且都等各自的任务B结束,线程池只有2个线程现在都在执行任务A所以两个任务B进入阻塞队列等待,而只有当线程1、2执行完任务A才会释放回池里执行任务B,所以陷入一种类似死锁的场景,线程饥饿。
/**
* 模拟线程饥饿
* */
public class ThreadStarvingTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
System.out.println(currentThreadName() + "接待客人");
Future<String> cook = executorService.submit(() -> {
System.out.println(currentThreadName() + "做回锅肉");
return "回锅肉";
});
try {
System.out.println(currentThreadName() + "上" + cook.get()); //Waits if necessary for the computation to complete, and thenretrieves its result.
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.submit(() -> {
System.out.println(currentThreadName() + "接待客人");
Future<String> cook = executorService.submit(() -> {
System.out.println(currentThreadName() + "做鱼香肉丝");
return "鱼香肉丝";
});
try {
System.out.println(currentThreadName() + "上" + cook.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
public static String currentThreadName() {
return Thread.currentThread().getName();
}
}
返回结果:
pool-1-thread-1接待客人
pool-1-thread-2接待客人
如果把fixed线程池大小改成3,就会执行下去了:
pool-1-thread-1接待客人
pool-1-thread-2接待客人
pool-1-thread-3做回锅肉
pool-1-thread-1上回锅肉
pool-1-thread-3做鱼香肉丝
pool-1-thread-2上鱼香肉丝
线程的daemon属性
上面我们会发现两种情况里,jvm进程最后都没结束,以往印象里main()这类hello world测试程序主线程跑完程序就退出结束了,或者是在之前搞过写死循环或者阻塞(比如SocketServer那类程序),然后程序一直执行。所以这里我们jstack可以看到main线程跑完了,那么jvm进程没结束,是因为线程池的两个线程还继续等待执行任务。
我们只要把线程池里的线程都设置为daemon=true,那么就可以在main线程结束以后,也结束jvm进程了:
实现一个ThreadFactory
import java.util.concurrent.ThreadFactory;
public class MyThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
然后用ThreadPoolExecutor
自定义线程池替换Executors.newFixedThreadPoll
:
ExecutorService executorService = new ThreadPoolExecutor(3, 3, 0, //coreSize, maxSize, keepalivetime都是0
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), //任务队列也跟原来一样
new MyThreadFactory(), //用自定义的ThreadFactory
new AbortPolicy()); //拒绝策略也跟原来一样,用Abort
执行结果:
Thread-0接待客人
Thread-1接待客人
Thread-2做回锅肉
Thread-0上回锅肉
Thread-2做鱼香肉丝
Thread-1上鱼香肉丝
main主线程结束
主线程结束了,它启动的线程池里边的线程又都是daemon线程,所以会随着主线成结束而结束,所以jvm进程也就结束了。