Reputation: 1070
I am trying to implement a kind of thread pool, that when I start a Thread, my own class that extends thread, its run method will run forever or until some condition is met.
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;
}
}
My main doubts are:
toCall
variable?setCallable
be safe?hasResult
Condition to be signaled.I'm trying to implement my own very simple ThreadPoolExecutor and my approach is to have a list of these Worker's, creating them upon need, and reusing those from this list that aren't doing anything.
If this approach is wrong whats the best way to achive this functionality?
Any help would be appreciated, thanks.
Upvotes: 2
Views: 159
Reputation: 54679
As already pointed out by AR.3 in his answer, the thread may not necessarily see the change in the toCall
variable.
Declaring the variable as volatile
is one way of making this point safe, but (without diving too deeply into the details of the Java Memory Model), this may not be necessary. More precisely: It may not be sufficient here, because there clearly is a race condition involving your toCall
variable:
| 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: |
So an external thread may set the toCall
variable to a non-null
value. The worker thread may find that toCall!=null
, and enter the body of the if
-statement. At this point, an external thread may set toCall
to null
, causing a NullPointerException
when the worker attempts to do toCall.call()
.
Of course, this could be fixed by wrapping accesses to the toCall
variable into synchronization blocks. And then, it would not have to be volatile
any more - that's what I referred to above.
There may be some other synchronization issues with your code. You did not give us all details, particularly about the role of the hasResult
variable. And you did not say whether there is a particular reason of why you wanted to implement this on your own.
But in general:
There are easier, more elegant and more idiomatic ways of achieving what you probably intended. One of the comments already pointed you to the Future
class, which offers the functionality that you seemingly wanted to implement there. Depending on where and how the synchronization (i.e. the waiting, in this case) should take place, there are different options. The easiest could be an executor service:
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
Alternatively, the CompletableFeature
class offers mechanisms for adding a "callback" that will be informed when the result is avaliable.
Upvotes: 1
Reputation: 72884
Does this thread see the change in toCall variable ?
In terms of visibility, with no synchronization whatsoever, the answer is not necessarily.
after it uses this variable and sets it to null, can another call to setCallable be safe ?
This is where the implementation may not be "safe". What happens if two threads submit a Callable
at the same time to the worker? Given the above code, it will process only one of them and ignore the other. A better alternative may be to have the workers poll a shared queue of tasks. You can use something like a BlockingQueue
to save the tasks. One of the methods there allow you to poll the queue with a timeout.
Finally is this a good way of having a thread always run and give it work when needed?
I don't think so, for the reason mentioned above.
Upvotes: 2