我正在尝试实现一种线程池,当我启动一个Thread时,我自己的类扩展线程,它的run方法将永远运行或直到满足某些条件。
private class Worker extends Thread {
private Callable<T> toCall;
private T result;
@Override
public void run(){
try{
while(true) {
if(toCall == null && /* Other condition accessing Outer class field */){
// Idle for too much time
break; // Finish thread run normally
}
if(toCall != null && /* Other condition accessing Outer class field */){
// Is this block reached when setCallable is invoked after this thread is running ?
result = toCall.call();
hasResult.signal(); // Condition variable
toCall = null;
}
}
}catch(InterruptedException ie){
// TODO
}
}
public void setCallable(Callable<T> toCall){
this.toCall = toCall;
}
public void getResult(){
return result;
}
}
我的主要疑虑是:
toCall
变量?setCallable
的另一次调用是否安全?hasResult
条件发出信号。我正在尝试实现我自己非常简单的ThreadPoolExecutor,我的方法是获得这些Worker的列表,根据需要创建它们,并重用那些没有做任何事情的列表。
如果这种方法出错,那么获得此功能的最佳方法是什么?
任何帮助将不胜感激,谢谢。
答案 0 :(得分:2)
此线程是否看到toCall变量的变化?
就可见性而言,没有任何同步,答案不一定。
使用此变量并将其设置为null后,对setCallable的另一次调用是否安全?
这是实施可能不“安全”的地方。如果两个线程同时向工作者提交Callable
会发生什么?鉴于上面的代码,它将只处理其中一个而忽略另一个。更好的选择可能是让工人轮询共享的任务队列。您可以使用类似BlockingQueue
的内容来保存任务。其中一种方法允许您使用超时轮询队列。
最后,这是一个让线程始终运行并在需要时让它工作的好方法吗?
由于上述原因,我不这么认为。
答案 1 :(得分:1)
正如AR.3 in his answer已经指出的那样,线程可能不一定会看到toCall
变量的变化。
将变量声明为volatile
是使这一点安全的一种方法,但是(没有深入研究Java内存模型的细节),这可能没有必要。更准确地说:这里可能没有足够,因为显然存在涉及toCall
变量的竞争条件:
| Worker thread: | External thread:
---| ------------------------------------------|--------------------------------
0: | while(true) { |
1: | | worker.setCallable(c);
2: | if(toCall != null){ |
3: | | worker.setCallable(null);
4: | result = toCall.call(); // CRASH! |
5: |
因此,外部线程可能会将toCall
变量设置为非null
值。工作线程可能会找到toCall!=null
,并输入if
- 声明的正文。此时,外部线程可能会将toCall
设置为null
,当工作人员尝试执行NullPointerException
时会导致toCall.call()
。
当然,这可以通过将对toCall
变量的访问包装到同步块中来解决。然后,它不再是volatile
- 这就是我上面提到的。
您的代码可能存在其他一些同步问题。您没有向我们提供所有细节,尤其是hasResult
变量的作用。而且你没有说出为什么你想要自己实现这一点的特殊原因。
但总的来说:
有更简单,更优雅,更惯用的方式来达到你想要的目标。其中一条评论已经向您指出Future
类,它提供了您似乎希望在那里实现的功能。根据同步的位置和方式(即等待,在这种情况下),应该有不同的选项。最简单的可能是执行者服务:
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<T> future = executorService.submit(toCall);
// Whereever this should be consumed:
T t = future.get(); // Will wait until the result is computed
或者,CompletableFeature
类提供添加“回调”的机制,当结果可用时将通知该回调。