【问题标题】:Confused about the "Print in Order" problem on LeetCode对 LeetCode 上的“按顺序打印”问题感到困惑
【发布时间】:2021-01-24 04:17:29
【问题描述】:

这应该是多线程的一个简单问题:https://leetcode.com/problems/print-in-order/ “Foo的同一个实例会被传递给三个不同的线程。线程A会调用first(),线程B会调用second(),线程C会调用third()。设计一个机制,修改程序为确保 second() 在 first() 之后执行,third() 在 second() 之后执行” 他们给出了这段代码:

public Foo() {}
    public void first(Runnable printFirst) throws InterruptedException {
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
    }
    public void second(Runnable printSecond) throws InterruptedException {
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
    }
    public void third(Runnable printThird) throws InterruptedException {
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
    }

**似乎我可以使用 Thread.join 解决它,如下所示,但我不明白的是为什么他们将 Runnable 的实例传递给每个方法,以及如何正确地做到这一点,因为下面的代码将打印每条消息两次——一次是因为 Thread.start() 将调用相应的 run() 方法,一次是直接调用该方法。我知道这是错误的方法,但是如果我们尝试使用 join 方法,则无法弄清楚什么是正确的解决方案。 **

public Foo() throws InterruptedException {
        Runnable r1 = () -> {
            System.out.println("first ");
        };
        first(r1);
        
        Runnable r2 = () -> {
            System.out.println("second ");
        };
        second(r2);
        
        Runnable r3 = () -> {
            System.out.println("third ");
        };
        third(r3);
        
        Thread t1 = new Thread(r1);
        t1.start();
        try {
            t1.join(); // wait for this thread to finish before starting #2
        }
        catch(Exception e) {
            System.err.println("Thread 1 error");
        }
        
        Thread t2 = new Thread(r2);
        t2.start();
        
        try {
            t2.join();
        }
        catch(Exception e) {
            System.err.println("Thread 2 error");
        }
        
        Thread t3 = new Thread(r3);
        t3.start();
        
        try {
            t3.join();
        }
        catch(Exception e) {
            System.err.println("Thread 3 error");
        }
    }```

【问题讨论】:

标签: java multithreading runnable


【解决方案1】:

Leetcode 是针对代码挑战的,所以我们不应该给出完整的解决方案,因为那对你来说就不是挑战了。

所以这里有一个提示: 使用两个 CountDownLatch 对象,一个通知方法second() 方法first() 完成,另一个通知方法@ 987654329@ 那个方法second() 完成了。阅读documentation 了解如何使用它。

在您阅读文档时,我建议您阅读package documentation,以详细了解可用于处理多线程代码的功能。


更新

为了更好地理解挑战,假设 Leetcode 正在使用这样的类来测试 Foo 类。

public class Test {
    public static void main(String[] args) throws Exception {
        Foo foo = new Foo();
        Thread t1 = new Thread(() -> call(foo::first, "first,"));
        Thread t2 = new Thread(() -> call(foo::second, "second,"));
        Thread t3 = new Thread(() -> call(foo::third, "third."));
        
        // Start threads out of order, with delay between them, giving each thread
        // enough time to complete, if not adequately coded to ensure execution order.
        t2.start();
        Thread.sleep(500);
        t3.start();
        Thread.sleep(500);
        t1.start();
        
        // Wait for threads to complete
        t2.join();
        t3.join();
        t1.join();
        
        // At this point, the program output should be "first,second,third."
    }
    interface FooMethod {
        public void call(Runnable printFirst) throws InterruptedException;
    }
    private static void call(FooMethod method, String text) {
        try {
            method.call(() -> System.out.print(text));
        } catch (InterruptedException e) {
            System.out.println(e);
        }
    }
}

您无法修改此代码,因为它对您隐藏。您必须以某种方式向 Foo 类添加代码,以确保以正确的顺序调用 3 个 Runnable 对象。

简单地将Thread.sleep() 调用添加到这 3 个方法不是正确的解决方案,因为无论通过下面的测试在线程启动之间添加多长时间的延迟,它都应该运行。

您必须使用某种线程同步功能,例如monitorsLocksSynchronizers

【讨论】:

  • 我看到了很多解决这个问题的方法。它们发布在 LeetCode 的 cmets 部分。那不是我的问题。我试图了解为什么将 Runnable 对象传递给每个函数以及如何使 Thread.join 版本工作。为什么 Thread.join 不是一个接一个地对线程进行排序的正确方法。
  • @user1385969 "为什么将 Runnable 对象传递给每个函数" 因为这 3 个方法将被 3 个不同的线程使用 3 个不同的 Runnable 对象调用,并且挑战是确保以正确的顺序调用它们。 “如何使 Thread.join 版本工作” 既然创建线程不是挑战的一部分,那这有什么关系呢? “为什么 Thread.join 不是正确的方式” 因为挑战在于修改 Foo 代码,特别是这 3 个方法,以便按照描述行为的看不见的框架将调用run() 正确的顺序。
  • @user1385969 Runnable 仅用于传递打印的函数,它与线程无关,它们不像您在代码中那样用于启动新线程。也许这就是让您感到困惑的地方?
【解决方案2】:

更简单的方法是将Semaphorerunacquirerelease 方法一起使用:

class Foo {
    Semaphore runSecond;
    Semaphore runThird;

    public Foo() {
        runSecond = new Semaphore(0);
        runThird = new Semaphore(0);
    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        runSecond.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        runSecond.acquire();
        printSecond.run();
        runThird.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        runThird.acquire();
        printThird.run();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-18
    • 1970-01-01
    • 1970-01-01
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多