题目描述:
我们提供了一个类:
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"。
解法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 Object lock = new Object();
private int flag = 0;
volatile int count = 1;
public Foo() {
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
count++;
// printFirst.run() outputs "first". Do not change or remove this line.
}
public void second(Runnable printSecond) throws InterruptedException {
while(count != 2);
printSecond.run();
count++;
// printSecond.run() outputs "second". Do not change or remove this line.
}
public void third(Runnable printThird) throws InterruptedException {
while(count != 3);
printThird.run();
// printThird.run() outputs "third". Do not change or remove this line.
}
}
解法2:利用volatile变量
class Foo {
volatile int count = 1;
public Foo() {
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
count++;
// printFirst.run() outputs "first". Do not change or remove this line.
}
public void second(Runnable printSecond) throws InterruptedException {
while(count != 2);
printSecond.run();
count++;
// printSecond.run() outputs "second". Do not change or remove this line.
}
public void third(Runnable printThird) throws InterruptedException {
while(count != 3);
printThird.run();
count = 1;
// printThird.run() outputs "third". Do not change or remove this line.
}
}
解法3:利用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方法执行完之后。
class Foo {
private CountDownLatch a;
private CountDownLatch b;
public Foo() {
a = new CountDownLatch(1);
b = new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
a.countDown();
// printFirst.run() outputs "first". Do not change or remove this line.
}
public void second(Runnable printSecond) throws InterruptedException {
a.await();
printSecond.run();
b.countDown();
// printSecond.run() outputs "second". Do not change or remove this line.
}
public void third(Runnable printThird) throws InterruptedException {
b.await();
printThird.run();
// printThird.run() outputs "third". Do not change or remove this line.
}
}
解法4:利用信号量Semaphore
Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
如果我们设置Semaphore 里初始值为0,就是一开始使线程阻塞从而完成其他执行。
原理和CountDownLatch 差不多。
first方法直接释放(初始值为0,是可以释放的)。
second方法在最开始会获取spa,只有first方法执行完之后,才能在此处获取到,即只有first执行完之后才会执行second。并释放spb。
third方法在最开始会获取spb,spb释放是在second执行完之后,因此只有在second执行完之后才会执行third。
class Foo {
private Semaphore a;
private Semaphore b;
public Foo() {
a = new Semaphore(0);
b = new Semaphore(0);
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
a.release();
// printFirst.run() outputs "first". Do not change or remove this line.
}
public void second(Runnable printSecond) throws InterruptedException {
a.acquire();
printSecond.run();
b.release();
// printSecond.run() outputs "second". Do not change or remove this line.
}
public void third(Runnable printThird) throws InterruptedException {
b.acquire();
printThird.run();
// printThird.run() outputs "third". Do not change or remove this line.
}
}
测试代码:
public static void main(String[] args) {
Foo foo =new Foo();
for (int i =0; i <8; i++) {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {
try {
foo.first(() -> {
System.out.println("one");
});
}catch (InterruptedException e) {
e.printStackTrace();
}
});
executor.submit(() -> {
try {
foo.second(() -> {
System.out.println("two");
});
}catch (InterruptedException e) {
e.printStackTrace();
}
});
executor.submit(() -> {
try {
foo.third(() -> {
System.out.println("three");
});
}catch (InterruptedException e) {
e.printStackTrace();
}
});
executor.isShutdown();
}
}