对LeetCode上的“顺序打印”问题感到困惑

问题描述

在多线程上,这应该是一个简单的问题: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");
        }
    }```

解决方法

Leetcode是针对代码挑战的,所以我们不应该提供完整的解决方案,因为那样对您来说就不是挑战。

所以这是一个提示:使用两个 CountDownLatch 对象,一个对象通知方法second()方法first()完成,另一个通知方法third()方法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对象。

仅对这3个方法添加Thread.sleep()调用是不正确的解决方案,因为无论下面的此测试可能在线程启动之间添加多长时间的延迟,此方法都应运行。

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

,

一种更简单的方法是将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();
    }
}