I was trying to write some linear algebra library in java and wanted to implement multithreading, using the CPU. For this, I've created a class ComputationMaster
that has 8 ComputationThread
's.
The idea is that when as task is given to the Master, it will give this task to all of the Threads and they will work on that.
My attempt was the following:
A task is a method that is called until it returns false
. The method itself needs to manage on what data it is working but this is not part of the question itself.
public interface ComputationMethod {
public boolean execute();
}
Now, let's talk about the ComputationThread: It extends Thread
and looks like this:
ComputationMethod computation;
public ComputationThread(){
super();
this.start();
}
public void run(){
while(!this.isInterrupted()){
try{
if(computation != null){
while(computation.execute()){}
computation = null;
ComputationMaster.notify_thread_finished();
}
}catch (Exception e){
e.printStackTrace();
this.interrupt();
}
}
this.interrupt();
}
You can see that it notifies the ComputationMaster that he finished the task because the task itself returned false
.
Finally, I will show you my attempt for my ComputationMaster
:
public static final int MAX_THREAD_AMOUNT = 8;
public static Thread MAIN_THREAD;
private static ComputationThread[] threads = new ComputationThread[MAX_THREAD_AMOUNT];
static int finished = 0;
static synchronized void notify_thread_finished(){
finished ++;
if(finished == MAX_THREAD_AMOUNT){
MAIN_THREAD.notifyAll();
finished = 0;
}
}
public static void compute(ComputationMethod method){
for(ComputationThread t:threads){
t.computation = method;
}
MAIN_THREAD = Thread.currentThread();
try {
MAIN_THREAD.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
The idea is that when the ComputationMaster
get's a method to calculate, it will give it to all the threads and wait until they are finished. I have not worked with waiting Threads yet so I tried saving the current Thread and let it continue, once the counter of finished Threads is equal to the amount of total threads.
This seemed pretty logical for me but there are multiple issues with my code:
IllegalMonitorStateException
is thrown. ComputationThreads
will go into an infinite loop and wait until a new task is given. (Perhaps this could also be done with letting them wait) I do not want to create a new thread every time a new task is given and destroy them once the task is finished.
I don't think you need all that signaling between threads. You can just use thread.join
Also, one minor design flaw is that you have the threads in an infinite spin loop until the computation
member is set. This will slow your initial performance a bit. You should set the computation
member BEFORE starting the thread. That is, don't let the constructor of ComputationThread
invoke thread.start
. Do that in your compute
function.
This is likely what you seek:
public static void compute(ComputationMethod method){
for(ComputationThread t:threads){
t.computation = method;
t.start();
}
// wait for all threads to finish
for(ComputationThread t:threads){
t.join();
}
}
Then your run function is simplified to:
public void run(){
try {
while(computation.execute()){}
}
catch (Exception e){
e.printStackTrace();
}
}
This is an example of how you can use package java.util.concurrent
to achieve your goal. First you want to build an ExecutorService
:
ExecutorService svc = Executors.newFixedThreadPool(10);
Here, I'm using one that relies on a fixed number of threads to execute tasks; the basic method to submit a task to it is execute()
:
svc.execute(new Runnable() {
@Override
public void run() {
System.out.println("Hello!");
}
});
However, this has the limitation that the called method has no return value, whereas instead you need to return a Boolean
. To do that, call submit()
:
Future<Boolean> submit = svc.submit(new Callable<Boolean>() {
@Override
public Boolean call() {
return true;
}
});
The above code can be simplified using a lambda expression , introduced with Java 8:
Future<Boolean> submit = svc.submit(() -> true);
This is a wrap-up:
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
public class Threads {
private final ExecutorService svc;
public Threads(ExecutorService svc) {
this.svc = svc;
}
public List<Future<Boolean>> execute(List<ComputationMethod> methods) throws InterruptedException {
return svc.invokeAll(methods.stream()
.map(im -> (Callable<Boolean>) im::execute)
.collect(Collectors.toList()));
}
}
A few notes here:
invokeAll()
instead of submit()
because I needed to process a list of computations, not only one ComputationMethod
into a list of Callable
using Streaming APIs introduced in Java 8 Callable
and used a lambda expression instead This is an example of how you can use the above class:
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
public class ThreadsTest {
@Test
public void runThreeCalculations() throws InterruptedException {
ExecutorService svc = Executors.newFixedThreadPool(10);
Threads threads = new Threads(svc);
List<Future<Boolean>> executions = threads.execute(Arrays.asList(
() -> true,
() -> true,
() -> true
));
svc.shutdown();
svc.awaitTermination(10, TimeUnit.SECONDS);
List<Boolean> results = executions.stream().map(
f -> {
try {
return f.get();
} catch (InterruptedException | ExecutionException e) {
throw new AssertionError(e);
}
}
).collect(Collectors.toList());
assertEquals(Arrays.asList(true, true, true), results);
}
}
In this self-contained example, which uses the JUnit testing framework, I'm shutting down the Executor
after I used it and I wait for it to shut down:
svc.shutdown();
svc.awaitTermination(10, TimeUnit.SECONDS);
However, in production cases, you might want to keep the executor around to be able to keep processing tasks.
On the use of Future
, which alone deserves a whole post, here's what you need to know to get started: it represents a computation that might end in the future, and you have a way to try and extract the computed value out of it.
In the above example, I use get()
to do that, but only because I'm sure the task has ended (since I shut down the executor, and waited 10 seconds in case of problems / hangs), but in general you can also
Future
has completed via method get(long timeout, TimeUnit unit)
cancel()
it isDone()
and isCancelled()
Hope this helps!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.