我在Java中使用ThreadPoolExecutor
来管理大量正在运行的线程。我已经创建了自己的简单ThreadFactory
,因此我可以为线程提供更好的名称。
问题是,首次创建线程池时,名称会在线程中设置,并且与线程池实际运行的任务无关。我理解这一点......我的Runnables和Callables - 尽管它们有名字 - 实际上是ThreadPoolExecutor
运行线程的一个抽象层次。
StackOverflow上还有一些关于为ThreadPoolExecutor
线程池创建名称的问题。 (请参阅How to give name to a callable Thread?和How to name the threads of a thread pool in Java。)
我想知道的是:有没有人有一个很好的解决方案来保持线程池线程的名称与它实际运行的Runnable同步?
即。如果我调用Thread.getCurrentThread().getName()
我希望不返回顶级线程池的名称,而是返回线程当前正在运行的Callable / Runnable的名称。< / p>
由于这主要是出于调试和日志记录的目的,我试图避免一个解决方案,这个解决方案涉及我将新代码放入可能提交给ThreadPoolExecutor的每个Runnable中 - 我只是将一些代码放入ThreadFactory中或者包装ThreadPoolExecutor本身,以便在一个地方完成更改。如果不存在这样的解决方案,我可能不会打扰,因为它不是关键任务。
开始编辑 为了澄清,我知道我可以将Thread.currentThread().setName( "my runnable name" );
作为每个Runnable运行方法的第一行,但我试图避免这样做。我在这里是一个完美主义者,我意识到这一点,所以如果人们想对这个问题发表评论并告诉我,我不会被冒犯。 结束编辑
我想,我的另一个问题是人们是否认为做这样的事情是个坏主意。我是否应该像这样更新线程池名称?
感谢您的任何建议!
答案 0 :(得分:14)
创建一个覆盖beforeExecute方法的ThreadPoolExecutor。
private final ThreadPoolExecutor executor = new ThreadPoolExecutor (new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()){
protected void beforeExecute(Thread t, Runnable r) {
t.setName(deriveRunnableName(r));
}
protected void afterExecute(Runnable r, Throwable t) {
Thread.currentThread().setName("");
}
protected <V> RunnableFuture<V> newTaskFor(final Runnable runnable, V v) {
return new FutureTask<V>(runnable, v) {
public String toString() {
return runnable.toString();
}
};
};
}
不确定derveRunnableName()
究竟有多精确,可能是toString()
?
编辑:Thread.currentThread()实际上是在beforeExecute中设置的线程,它调用afterExecute。您可以引用Thread.currentThread(),然后在afterExecute中设置名称。这在javadocs
中有说明/**
* Method invoked upon completion of execution of the given Runnable.
* This method is invoked by the thread that executed the task. If
* non-null, the Throwable is the uncaught <tt>RuntimeException</tt>
* or <tt>Error</tt> that caused execution to terminate abruptly.
*
* <p><b>Note:</b> When actions are enclosed in tasks (such as
* {@link FutureTask}) either explicitly or via methods such as
* <tt>submit</tt>, these task objects catch and maintain
* computational exceptions, and so they do not cause abrupt
* termination, and the internal exceptions are <em>not</em>
* passed to this method.
*
* <p>This implementation does nothing, but may be customized in
* subclasses. Note: To properly nest multiple overridings, subclasses
* should generally invoke <tt>super.afterExecute</tt> at the
* beginning of this method.
*
* @param r the runnable that has completed.
* @param t the exception that caused termination, or null if
* execution completed normally.
*/
protected void afterExecute(Runnable r, Throwable t) { }
编辑 TPE会将Runnable包装在FutureTask中,因此要支持toString
方法,您可以覆盖newTaskFor
并创建自己的包装FutureTask。
答案 1 :(得分:4)
所以,我有一个解决方案,既可以设置名称,也可以在名称后进行清理。感谢Peter Lawrey和John Vint提出的建议。由于这两个建议都没有完全解决我的问题,我想我会将此示例代码作为单独的答案发布。如果这是一个糟糕的礼仪,我道歉 - 如果是这样,请告诉我,我会调整。
在下面的代码中,我决定保留原始ThreadPoolExecutor线程的名称并追加Runnable名称,然后在finally块中删除Runnable名称以进行清理,但这很容易改变。
正如John Vint建议的那样,我宁愿覆盖beforeExecution
方法,然后覆盖afterExecution
方法进行清理,但afterExecution
没有线程句柄
public class RunnableNameThreadPoolExecutor extends ThreadPoolExecutor {
/* Constructors... */
@Override
public void execute(Runnable command) {
super.execute(new ManageNameRunnable(command));
}
private class ManageNameRunnable implements Runnable {
private final Runnable command;
ManageNameRunnable( Runnable command ) {
this.command = command;
}
public void run() {
String originalName = Thread.currentThread().getName();
try {
String runnableName = getRunnableName(command);
Thread.currentThread().setName(originalName+ ": " + runnableName);
command.run();
} finally {
Thread.currentThread().setName(originalName);
}
}
}
}
答案 2 :(得分:2)
我的建议是尝试
pool.execute(new Runnable() {
public void run() {
Thread.getCurrentThread().setName("My descriptive Runnable");
// do my descriptive Runnable
}
});
如果您愿意,也可以在完成后重置名称。