代码
先来看这么一段代码
判断一下是否有问题?
public class ThreadPoolLock {
/**
* 固定大小为2的线程池
*/
private static ExecutorService pool = Executors.newFixedThreadPool(2);
/**
* 测试
* 开了十个父任务
*/
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
pool.submit(new PTask(pool));
}
}
}
/**
* 父任务
*/
class PTask implements Runnable {
private int subNum = 3;
private ExecutorService pool;
public PTask(ExecutorService pool) {
this.pool = pool;
}
/**
* 一个父任务下要执行完subNum=3个子任务
* 这个父任务才算执行完成
* 否则会一直await
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"父任务");
CountDownLatch latch = new CountDownLatch(subNum);
for (int i = 0; i < subNum; i++) {
pool.execute(new STask(i,latch));
}
// 父任务阻塞 等待子任务执行完成
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 子任务
*/
class STask implements Runnable {
private int num;
private CountDownLatch latch;
public STask(int num, CountDownLatch latch) {
this.num = num;
this.latch = latch;
}
/**
* 执行完成一个子任务
* countDown
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"子任务"+num);
latch.countDown();
}
}
解析
运行结果
就打印了两个父任务的执行,就没动静了。
我们看一下线程的状态
- 开发环境下,idea可以点击“相机图标”查看线程信息
- 要是在服务器上,我们还是老老实实的用命令查看线程信息
14329 Jps
14317 Launcher
14318 ThreadPoolLock
apple$ jstack 14318 > ./thread.txt
apple$ cat thread.txt
2021-07-04 22:43:22
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):
"Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007fb5748d4000 nid=0x3e03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fb5740ae000 nid=0x1903 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"pool-1-thread-2" #11 prio=5 os_prio=31 tid=0x00007fb574099800 nid=0x3b03 waiting on condition [0x0000700003168000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000079596a9c8> (a java.util.concurrent.CountDownLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
at cn.itcast.pool.PTask.run(ThreadPoolLock.java:49)
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:748)
"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007fb5748d3000 nid=0x4103 waiting on condition [0x0000700003065000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007958c5268> (a java.util.concurrent.CountDownLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
at cn.itcast.pool.PTask.run(ThreadPoolLock.java:49)
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:748)
...省略
根据以上信息,我们发现线程都处于waitting状态,
可以定位到再at cn.itcast.pool.PTask.run(ThreadPoolLock.java:49) 49行
在回过头来看代码
发现阻塞在了这里
CountDownLatch.await()
会阻塞,等待(子)线程执行完成,并且countDown()
到0后,才会继续执行
那么问题来了?为啥阻塞在这里?因为子任务没有执行。为啥没有执行?因为线程池里没有线程。
原因分析
newFixedThreadPool:固定线程数量的线程池,核心线程 = 最大线程,是无边队列
代码里 我们用了
ExecutorService pool = Executors.newFixedThreadPool(2);
开了两个线程去跑任务,输出的结果为
pool-1-thread-1父任务
pool-1-thread-2父任务
也就是说,这两个线程执行了父任务。
继续回过头查看代码
打印完成,向线程池提交子任务完成,阻塞完成。
然后,没有线程了。2个线程都跑了父任务。
子任务没有线程执行。
于是…
父任务等待着子任务执行完成好释放锁。
子任务又等着父任务执行完成好释放锁。
…
死锁产生了。
总结
主要原因还是父子线程共用了一个线程池,线程的分配不合理造成。
所以最好还是自定义线程吧(参考)。
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,
1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));