繁体   English   中英

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

[英]How to let a thread wait until task is finished?

我试图在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得到一个计算方法时,它会将它提供给所有线程并等到它们完成。 我还没有等待Threads,所以我尝试保存当前的Thread并让它继续,一旦完成的Threads的计数器等于总线程的数量。

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

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

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

我不认为你需要线程之间的所有信号。 你可以使用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();
   }
}

这是一个如何使用包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表达式可以简化上面的代码:

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 APIComputationMethod列表转换为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秒),但一般情况下你可以也

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

希望这可以帮助!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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