题目如下
我们提供了一个类:
public class Foo {
public void one() { print("one"); }
public void two() { print("two"); }
public void three() { print("three"); }
}
三个不同的线程将会共用一个 Foo 实例。
线程 A 将会调用 one() 方法
线程 B 将会调用 two() 方法
线程 C 将会调用 three() 方法
请设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。
示例 1:
输入: [1,2,3]
输出: "onetwothree"
解释:
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 one() 方法,线程 B 将会调用 two() 方法,线程 C 将会调用 three() 方法。
正确的输出是 "onetwothree"。
示例 2:
输入: [1,3,2]
输出: "onetwothree"
解释:
输入 [1,3,2] 表示线程 A 将会调用 one() 方法,线程 B 将会调用 three() 方法,线程 C 将会调用 two() 方法。
正确的输出是 "onetwothree"。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/print-in-order
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 解法1:利用锁、成员变量来控制顺序。
first方法直接打印one,并设置flag = 1,并唤醒其他所有线程。
second方法 如果flag != 1 那么轮询,并等待。当first方法执行完后,那么flag = 1,此时second会执行while块之后的,即打印two,并设置flag = 2。如first执行完后,third方法先执行,因此时flag =1,那么third方法会一直在轮询。
third方法在second执行完后,flag = 2,并唤醒其他所有线程,此时处于wait状态的只有third,因此,在second执行完后即会执行third方法。
class Foo {
private int flag = 0;
private Object lock = new Object();
public Foo() {
}
public void first(Runnable printFirst) throws InterruptedException {
synchronized (lock) {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
flag = 1;
lock.notifyAll();
}
}
public void second(Runnable printSecond) throws InterruptedException {
synchronized (lock) {
while(flag != 1) {
lock.wait();
}
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
flag = 2;
lock.notifyAll();
}
}
public void third(Runnable printThird) throws InterruptedException {
synchronized (lock) {
while(flag != 2) {
lock.wait();
}
printThird.run();
}
}
}
- 解法2:利用CountDownLatch
CountDownLatch是java.util.concurrent包下面的一个工具类,可以用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。 它可以允许一个或者多个线程等待其他线程完成操作。
简单点说,直到CountDownLatch里面计数为0才执行所有线程。
首先初始化两个数量均为1的计数器
first方法,在执行cdla.countDown();之后 cdla 计数为0。
second方法,cdla.await(); 如果cdla里计数不为0,那么会一直阻塞在此,直到cdla计数为0,即first方法执行完之后,才会通过。此时再执行cdlb.countDown();,cdlb计数为0。
third方法cdlb.await();如果cdlb里计数不为0,那么会一直阻塞在此,直到cdlb计数为0,即second方法执行完之后。
···
import java.util.concurrent.CountDownLatch;
class Foo {
private CountDownLatch cdla;
private CountDownLatch cdlb;
public Foo() {
cdla = new CountDownLatch(1);
cdlb = new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
cdla.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
cdla.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
cdlb.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
cdlb.await();
printThird.run();
}
}
···
- 解法3:利用信号量Semaphore
Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
如果我们设置Semaphore 里初始值为0,就是一开始使线程阻塞从而完成其他执行。
原理和CountDownLatch 差不多。
first方法直接释放(初始值为0,是可以释放的)。
second方法在最开始会获取spa,只有first方法执行完之后,才能在此处获取到,即只有first执行完之后才会执行second。并释放spb。
third方法在最开始会获取spb,spb释放是在second执行完之后,因此只有在second执行完之后才会执行third。
class Foo {
private Semaphore spa, spb;
public Foo() {
spa = new Semaphore(0);
spb = new Semaphore(0);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
spa.release();
}
public void second(Runnable printSecond) throws InterruptedException {
spa.acquire();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
spb.release();
}
public void third(Runnable printThird) throws InterruptedException {
spb.acquire();
printThird.run();
}
}
测试代码
public class Thread0001 {
public static void main(String[] args) throws InterruptedException {
Foo foo = new Foo();
Thread t1 = new Thread(() -> {
try {
foo.first(() -> {
System.out.println("one");
});
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
foo.second(() -> {
System.out.println("two");
});
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t3 = new Thread(() -> {
try {
foo.third(() -> {
System.out.println("three");
});
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t3.start();
}
}
结果:无论执行多少次均需一致