对于LeetCode上关于“按顺序打印”问题感到困惑。

huangapple go评论77阅读模式
英文:

Confused about the "Print in Order" problem on 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() 输出 "first"。请勿更改或删除此行。
    printFirst.run();
}
public void second(Runnable printSecond) throws InterruptedException {
    // printSecond.run() 输出 "second"。请勿更改或删除此行。
    printSecond.run();
}
public void third(Runnable printThird) throws InterruptedException {
    // printThird.run() 输出 "third"。请勿更改或删除此行。
    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(); // 等待线程1完成后再启动线程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");
    }
}
英文:

This should be an easy problem on multithreading: https://leetcode.com/problems/print-in-order/
"The same instance of Foo will be passed to three different threads. Thread A will call first(), thread B will call second(), and thread C will call third(). Design a mechanism and modify the program to ensure that second() is executed after first(), and third() is executed after second()" And they give this code:

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

**Seems I can solve it using Thread.join as below, but what I don't understand is why they pass the instance of Runnable to each method, and how to properly do it because the below code will print each message twice - once because Thread.start() will invoke the corresponding run() method, and once from calling that method directly. I understand that this is the wrong way to do it, but can't figure out what is the right solution, if we try to utilize the join method. **

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

</details>


# 答案1
**得分**: 3

一个更简单的方法是只使用`Semaphore`以及其中的`run`、`acquire`、`release`方法:

```java
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();
    }
}
英文:

A much easier way would be to just use Semaphore with run, acquire, release methods:

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

答案2

得分: 2

Leetcode 用于代码挑战,所以我们不应该提供完整的解决方案,因为那样对你来说就不会是一个挑战了。

这里有一个提示: 使用两个 CountDownLatch 对象,一个用于通知 second() 方法已经完成了 first() 方法,另一个用于通知 third() 方法已经完成了 second() 方法。阅读 文档 以了解如何使用它。

当你阅读文档时,我建议你阅读 包文档,以了解有关处理多线程代码的可用功能的更多信息。


更新

为了更好地理解这个挑战,假设 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."));

        // 以无序的方式启动线程,并在它们之间添加延迟,以便每个线程有足够的时间来完成,如果没有足够的编码来确保执行顺序。
        t2.start();
        Thread.sleep(500);
        t3.start();
        Thread.sleep(500);
        t1.start();

        // 等待线程完成
        t2.join();
        t3.join();
        t1.join();

        // 在这一点上,程序的输出应该是 "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

英文:

Leetcode is for code challenges, so we should not give complete solutions, because that wouldn't then be a challenge for you.

So here's a hint: Use two CountDownLatch objects, one to inform method second() that method first() is done, the other to inform method third() that method second() is done. Read the documentation to learn how to use it.

While you are reading through the documentation, I recommend you read the package documentation, to learn more about what features are available for handling multi-threaded code.


UPDATE

To better understand the challenge, assume that Leetcode is using a class like this to test the Foo class.

public class Test {
	public static void main(String[] args) throws Exception {
		Foo foo = new Foo();
		Thread t1 = new Thread(() -&gt; call(foo::first, &quot;first,&quot;));
		Thread t2 = new Thread(() -&gt; call(foo::second, &quot;second,&quot;));
		Thread t3 = new Thread(() -&gt; call(foo::third, &quot;third.&quot;));
		
		// 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 &quot;first,second,third.&quot;
	}
	interface FooMethod {
		public void call(Runnable printFirst) throws InterruptedException;
	}
	private static void call(FooMethod method, String text) {
		try {
			method.call(() -&gt; System.out.print(text));
		} catch (InterruptedException e) {
			System.out.println(e);
		}
	}
}

You cannot modify this code, as it is hidden from you. You have to somehow add code to the Foo class to ensure the 3 Runnable objects are called in the correct order.

Simply adding Thread.sleep() calls to the 3 methods is not the right solution, since this should run regardless of how long of a delay might be added between thread starts by this test below.

You have to use some kind of thread synchronization feature, e.g. monitors, Locks, or Synchronizers.

huangapple
  • 本文由 发表于 2020年10月9日 12:38:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/64274008.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定