当我尝试终止ExecutorService时,GAE会永远阻塞.以下小样本:
ThreadFactory threadFactory = ThreadManager.currentRequestThreadFactory();
ExecutorService pool = Executors.newSingleThreadExecutor(threadFactory);
Future<String> future = pool.submit(new Callable<String>() {
public String call() throws Exception {
return "Hello from Thread";
}
});
LOG.info("Result is: [" + future.get() + "]. Pool expected to be idle now");
pool.shutdown();
if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
LOG.info("Pool does not like shutdown()");
pool.shutdownNow();
if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
LOG.info("Pool does not even like shutdownNow()");
}
}
在本地运行时,相同的代码无阻塞地工作,在AppEngine上部署时,它只是阻塞而不会终止.超时可以增加,直到60秒的请求限制强制代码中断.
这似乎是标准JVM的一个微妙但危险的区别.经常发现清理代码可能会导致您的服务中断. ThreadManager
documentation提到线程有点特殊但它们是 – 据我所知 – 可中断并且意味着终止.
>只是我(一些图书馆弄乱线程)?
>这是一个错误/功能/某处记录?
因为等待终止只是毫无意义,可以调用pool.shutdown(),然后假设一切都没问题吗?运行线程是泄漏内存的好方法.
更新#1
经过一些测试后,我更加困惑.直接使用Thread时一切正常.稍微复杂的例子:
final CountDownLatch threadEnter = new CountDownLatch(1);
final Object wait4Interrupt = new Object();
Runnable task = new Runnable() {
public void run() {
synchronized (wait4Interrupt) {
threadEnter.countDown();
try {
wait4Interrupt.wait();
} catch (InterruptedException e) {
// expected to happen since nothing is going to notify()
LOG.info("Thread got interrupted.");
Thread.currentThread().interrupt();
}
}
}
};
Thread thread = ThreadManager.createThreadForCurrentRequest(task);
// not started state
LOG.info("Thread log #1: " + thread + " " + thread.getState());
thread.start();
threadEnter.await();
// thread is inside synchronized / already waiting
synchronized (wait4Interrupt) {
// => guaranteed that thread is in waiting state here
LOG.info("Thread log #2: " + thread + " " + thread.getState());
thread.interrupt();
}
thread.join(1000);
// thread is dead
LOG.info("Thread log #3: " + thread + " " + thread.getState());
生成的日志:
I 16:08:37.213 Thread log #1: Thread[Thread-7,5,Request #0] NEW
I 16:08:37.216 Thread log #2: Thread[Thread-7,5,Request #0] WAITING
I 16:08:37.216 Thread got interrupted.
I 16:08:37.217 Thread log #3: Thread[Thread-7,5,] TERMINATED
工厂返回的线程没有启动,它支持wait&中断就好了,它可以是join()’d并在之后终止. ExecutorService还想做什么?
更新#2
shutdown()导致示例#1中的pool.toString()
java.util.concurrent.ThreadPoolExecutor@175434a
[Shutting down, pool size = 1, active threads = 0, queued tasks = 0, completed tasks = 1]
这也表明它不是由未终止线程引起的问题,因为它表明活动线程= 0.
更新#3
池在完成任务之前被告知这样做会很好地关闭. 500 ms后,以下内容正确终止.添加future.get()将再次显示原始问题.
Future<String> future = pool.submit(new Callable<String>() {
public String call() throws Exception {
// sleep a bit so pool is "busy" when we're trying to shutdown.
Thread.sleep(500);
return "Hello from Thread";
}
});
// get here = evil
pool.shutdown();
pool.awaitTermination(2, TimeUnit.SECONDS);
=>问题似乎只发生在空闲池上.繁忙的游泳池可以关闭.
最佳答案 你是对的,App Engine上的线程是可以中断的.引自
official docs:
An application can perform operations against the current thread, such as
thread.interrupt()
.
由于它在本地工作正常,因此在生产环境中开发服务器和沙箱之间存在差异.
我认为开发服务器允许多线程执行,如果没有禁用,而生产环境需要在application config file (appengine-web.xml
)中明确说明它:
<threadsafe>true</threadsafe>
除非您明确声明您的应用程序是线程安全的,否则提供请求只能使用1个线程,因此您的ExecutorService无法启动新线程来执行您提交的任务,因此future.get()将阻止.它会阻塞,直到“当前”线程结束,但显然只能在提供请求后发生,所以你在这里遇到了死锁.