如何让线程等待任务完成?

时间:2019-04-21 09:30:54

标签: java multithreading

我试图用Java编写一些线性代数库,并想使用CPU实现多线程。为此,我创建了一个类ComputationMaster,它有8个ComputationThread

这个想法是,当将任务分配给主服务器时,它将把该任务分配给所有线程,它们将在该线程上工作。

我的尝试如下:

任务是在返回false之前一直被调用的方法。 该方法本身需要管理正在使用的数据,但这不是问题本身的一部分。

public interface ComputationMethod {
    public boolean execute();
}

现在,让我们谈谈ComputationThread: 它扩展了Thread,看起来像这样:

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();
}

您会看到它通知ComputationMaster他完成了任务,因为任务本身返回了false

最后,我将向您展示我为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();
    }
}

这个想法是,当ComputationMaster get是一种计算方法时,它将把它分配给所有线程并等待它们完成。 我还没有使用等待的线程,所以一旦完成线程的计数器等于总线程数,我尝试保存当前线程并继续执行。

这对我来说似乎很合逻辑,但是我的代码存在多个问题:

  1. 抛出IllegalMonitorStateException
  2. 假设任务已完成,ComputationThreads将进入无限循环并等待直到给出新任务。 (也许也可以通过让他们等待来完成)

我不想每次给出新任务时都创建一个新线程,并在任务完成后销毁它们。

2 个答案:

答案 0 :(得分:0)

我认为您不需要线程之间的所有信令。您可以只使用thread.join

此外,一个较小的设计缺陷是在设置computation成员之前,线程处于无限旋转循环中。这会降低您的初始性能。您应该在启动线程之前设置computation成员。也就是说,不要让ComputationThread的构造函数调用thread.start。在您的compute函数中执行此操作。

这可能是您想要的:

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();
    }
}

然后将运行功能简化为:

public void run(){

   try {
       while(computation.execute()){}
   }
   catch (Exception e){
        e.printStackTrace();
   }
}

答案 1 :(得分:0)

这是如何使用软件包java.util.concurrent实现目标的示例。首先,您要构建一个ExecutorService

ExecutorService svc = Executors.newFixedThreadPool(10);

在这里,我使用的是一个依赖于固定数量线程的线程来执行任务;向其提交任务的基本方法是execute()

svc.execute(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello!");
    }
});

但是,这有一个局限性,即被调用的方法没有返回值,而您需要返回一个Boolean。为此,请致电submit()

Future<Boolean> submit = svc.submit(new Callable<Boolean>() {
    @Override
    public Boolean call() {
        return true;
    }
});

可以使用Java 8引入的lambda expression来简化上述代码:

Future<Boolean> submit = svc.submit(() -> true);

这是一个总结:

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()));
    }
}

一些注意事项:

  • 我使用invokeAll()而不是submit(),因为我需要处理一系列计算,而不仅仅是一个
  • 我使用Java 8中引入的Streaming APIsComputationMethod的列表转换为Callable的列表
  • 我避免为每个Callable创建一个匿名类,而是使用lambda表达式

这是如何使用上述类的示例:

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);
    }
}

在这个使用JUnit测试框架的独立示例中,我在使用Executor之后将其关闭,并等待其关闭:

svc.shutdown();
svc.awaitTermination(10, TimeUnit.SECONDS);

但是,在生产情况下,您可能希望保留执行程序,以便能够继续处理任务。

使用Future(仅值得一篇完整的帖子),这是您开始需要了解的内容:它代表了可能在将来结束的计算,并且您可以尝试提取计算出来的值。

在上面的示例中,我使用get()来执行此操作,但这仅是因为我确定任务已结束(因为我关闭了执行程序,并在出现问题/挂起时等待了10秒钟),但通常您也可以

  • 检查Future是否已通过方法get(long timeout, TimeUnit unit)完成
  • cancel()
  • 通过isDone()isCancelled()检查任务是完成还是取消

希望这会有所帮助!