闲话少说,
Tomcat日志报错及堆栈信息:
因为这个问题,挂了几个节点。
跟踪报错的代码发现代码中用到future.get()方法:
Callable
任务由线程池提供的线程执行,那么这时候主线程则会阻塞,直到任务线程唤醒它们。获取结果时,通过future.get()方法获取:
当 s<= COMPLETING时,表明任务仍然在执行且没有被取消。如果它为true,那么走到awaitDone方法。
下面来看看awaitDone方法里面干了啥:
/**
* Awaits completion or aborts on interrupt or timeout.
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
一屏截图不完整,主要截图引起阻塞的判断条件:
如果给定的时间是非0(负数)或者给定的时间(正数, 时间单位时毫秒)已经过去了(0的时候会一直阻塞着)
此处因为网络或者其他原因,导致主线程调用future.get()方法时,任务子线程还没有从对接的第三方获取到数据,致使子线程挂起(等待唤醒),从而一直消耗线程池中的资源。
建议修改方案:去掉多线程操作;或者调用future.get(long timeout, TimeUnit unit)方法;
因为当任务正常结束或者异常时,都会调用finishCompletion去唤醒等待线程。
查看官方文档,寻找park方法:
字面理解park,就算占住,停车的时候不就把这个车位给占住了么?起这个名字还是很形象的。unpark,占住的反义词,就是释放。把车从车位上开走。
翻译一下:
- park:阻塞当前线程,(1)当配对的unpark发生或者(2)配对的unpark已经发生或者线程被中断时恢复(unpark先行,再执行park)。 (3)当absolute是false时,如果给定的时间是非0(负数)或者给定的时间(正数, 时间单位时毫秒)已经过去了(0的时候会一直阻塞着)。(4)当Absolute是true时,如果给定的时间(时间单位是纳秒)过去了或者伪造的(在我理解是参数不合法时)线程会恢复中断。这个操作是不安全的,所以在其他调用会很奇怪(奇怪?反正就是用的时候要小心)
- unpark:当指定线程被park命令阻塞时unpark命令可以恢复阻塞。在park命令没有被先调用过的时候,调用unpark,线程仍然不被阻塞。
参考文章:
java.util.concurrent.locks.LockSupport类详解:https://my.oschina.net/readjava/blog/282882
futureTask:http://www.cnblogs.com/maypattis/p/5827671.html