Thread.join() 在 ExecutorService 执行线程时不起作用

Thread.join() does not work when executing the Thread by ExecutorService

这是我的代码:

public static void main(String[] args) {
        System.out.println("program started");
        ExecutorService executor = Executors.newCachedThreadPool();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread finished");
            }
        });
        executor.execute(thread);
        try {
            thread.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("thread joined");

    }

当我如上所示启动 Thread 时,thread.join() 不起作用并且它不会等待 Thread 完成。我需要通过 ExecutorService 执行我的 Thread 并等待该线程完成。但我的代码效果不佳。谁能帮帮我?

为什么我不使用 Future 而不是 Thread

因为有时我需要打断我的 Thread 并等待 Thread 完成。但是当我取消 Future 时,future.get() 得到一个异常并且不等待它的 Thread 完成。

如果我的句子语法不正确,我提前道歉。因为我英语说得不好。

你的错误很微妙。

请注意 Executor.execute() 的 API:它需要一个 Runnable。而 Thread 实现了 Runnable,因此它可以传递给 Executor。但是,由 Executors.newCachedThreadPool() 编辑的实现 return 仅使用您传递的 Thread 实例的 run() 方法。 Thread 本身永远不会启动,因为执行程序使用它在其线程池中内部管理的线程。

解决方案很简单:Executors.newCachedThreadPool() 重新调整 ExecutorService,改用它的 submit(Runnable) 方法。您将获得 Future<?> 作为 return 值。在未来调用 get()Thread.join()

具有相同的效果

有关如何使用 ExecutorService 的更详细说明,您可以查看此处:http://tutorials.jenkov.com/java-util-concurrent/executorservice.html.

简单的回答:不要那样做。

不要像这样混合抽象层。 Executor 接口没有 execute() 个线程。它需要 Runnables。将 Thread 对象传递给它并不重要,除了调用 run() 方法外,您的线程根本不会被使用。

将"low layer" 裸铁线程与抽象的执行器服务混合是一个坏主意。

线程池 概念的重点是您而不是 试图控制底层线程。等待池线程结束 根本没有意义。线程池保留线程,因为建立线程是一项(相对)昂贵的操作。所以他们并没有结束,而是继续生活,在未来做其他工作。

这里真正的答案是:要么不要使用该执行程序服务,要么寻找适用于该概念的解决方案(无需您进入并在旁边做低级的事情)。

以及 "real real" 答案:退后一步,告诉我们您打算通过这种方式解决的 "real" 问题。

当您使用 ExecutorService 时,您应该将其传递给 RunnableThreadRunnable,但如果将它传递给 ExecutorService.

,则不会将其用作线程

ExecutorService 有另一种方法,submit,它允许您等待 Runnable(或 Callable,如果您想 return一个结果)

您可以将代码更改为:

public static void main(String[] args) {
    System.out.println("program started");
    ExecutorService executor = Executors.newCachedThreadPool();
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread finished");
        }
    };
    Future<?> future = executor.submit(runnable);
    try {
        future.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("waited for completion");
}

提交的任务将作为 Runnable 对象由另一个线程执行。这就是 join 不会阻塞主线程的原因。

您的代码等同于:

public static void main(String[] args) {
    System.out.println("program started");
    ExecutorService executor = Executors.newCachedThreadPool();

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread finished");
        }
    };

    executor.execute(runnable);

    try {
        Thread thread = new Thread(runnable);
        thread.join();  // the thread never start, so it will not block main thread
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("thread joined");
}

您可以尝试打印线程名称:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread()); // Thread[pool-1-thread-1,5,main]
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread finished");
    }
});
executor.execute(thread);

System.out.println(thread); // Thread[Thread-0,5,main]
  1. 线程 class 扩展了可运行接口。因此,任何接受 Runnable 类型参数的方法都将接受 Thread 的实例(或 class 实现 Runnable 接口的任何实例)。

  2. Executor.java(接口)有一个方法execute(Runnable command)Executors.newCachedThreadPool()提供的一个实现,使用了自己独立的内部Thread,同样启动。它使用 execute(Runnable command) 方法中传递的 Runnable 命令并执行 run() 方法中存在的语句。

  3. 您提供了一个 Thread 实例(它实现了 Runnable)因此内部线程(如第 2 点所述)调用了 运行 方法但从不调用 start() 方法在你的线程上。 execute() 方法接受您传递的实例,但 它只知道它是 Runnable 类型,它不知道方法 start() 存在 。不知道它不能调用它。任何其他程序员都可以像 class Task implements Runnable 一样传递 class 的实例,并且可以拥有像 invokeMeIAmImportantMethod() 这样的方法。但是 Executor 知道的任何方法都将是唯一的,并且只有 public void run() 定义在 Runnable 接口的合同中。

  4. 方法 join()start() 之前调用您的线程实例不会使当前线程等待您的线程完成(您的线程从未启动)。

A) 当我们使用了 newCachedThreadPool() 时,这意味着我们需要一个线程池服务。这意味着线程池服务将管理线程并执行您的任务(或命令)。没有必要启动线程来执行任务并期望也启动单独的线程池。它有点混乱(有点混乱)的逻辑。 因此,要么删除线程池并启动您的线程,要么删除您的线程并仅依赖于线程池。

B) 另外我建议你使用调试。您可以调试并找到线程的状态(它本来是新的)。这会让您进行更多研究,最终您会查看 ExecutorService execute() 方法所期望的参数。