java – ExecutorService#awaitTermination永远阻止 – 在GAE上破解/特殊?

当我尝试终止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()将阻止.它会阻塞,直到“当前”线程结束,但显然只能在提供请求后发生,所以你在这里遇到了死锁.

点赞