简体   繁体   中英

How to let a thread wait until task is finished?

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:

  1. An IllegalMonitorStateException is thrown.
  2. Assuming that the task has finished, the 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:

  • I'm using invokeAll() instead of submit() because I needed to process a list of computations, not only one
  • I transformed the list of ComputationMethod into a list of Callable using Streaming APIs introduced in Java 8
  • I avoided creating an anonymous class for every 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

  • check whether a Future has completed via method get(long timeout, TimeUnit unit)
  • cancel() it
  • check whether the task is done or canceled via 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM