前言
Java 多线程解题常用方法 & 工具类
类型 | 方法 | 说明 |
---|---|---|
synchronized | Object.wait(), Object.notify(), Object.notifyAll() | 每一个用 synchronized 修饰的部分都是一个临界区,同一时间只允许一个线程访问 |
Lock | Lock.lock(), Lock.unlock() | 具体实现类:ReentrantLock |
Semaphore | Semaphore.acquire(), Semaphore.release() | 信号量是一种计数器,用来保护一个或多个共享资源的访问 |
CountDownLatch | CountDownLatch.await(), CountDownLatch.countDown() | 在完成一组正在其他线程中执行的操作之前,允许线程一直等待 |
CyclicBarrier | CyclicBarrier.await() | 允许多个线程在某个集合点处进行互相等待 |
Phaser | Phaser.arriveAndAwaitAdvance() | 把并发任务分成多个阶段运行,在开始下一阶段之前,当前阶段中的所有线程都必须执行完成 |
具体问题
按序打印
三个不同的线程将会共用一个 Foo 实例。
- 线程 A 将会调用 one() 方法
- 线程 B 将会调用 two() 方法
- 线程 C 将会调用 three() 方法
请设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。
示例
输入:[1,3,2]
输出:"onetwothree"
解释:输入 [1,3,2] 表示线程 A 将会调用 one() 方法,线程 B 将会调用 three() 方法,线程 C 将会调用 two() 方法。
正确的输出是 "onetwothree"。
解题思路
/* 解法一:synchronized */
class Foo {
private Object lock;
private boolean firstFlag, secondFlag;
public Foo() {
this.lock = new Object();
this.firstFlag = false;
this.secondFlag = false;
}
public void first(Runnable printFirst) throws InterruptedException {
synchronized (lock) {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
firstFlag = true;
lock.notifyAll();
}
}
public void second(Runnable printSecond) throws InterruptedException {
synchronized (lock) {
while (!firstFlag) {
lock.wait();
}
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
secondFlag = true;
lock.notifyAll();
}
}
public void third(Runnable printThird) throws InterruptedException {
synchronized (lock) {
while (!secondFlag) {
lock.wait();
}
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
}
/* 解法二:Semaphore */
class Foo {
private Semaphore semaphoreFirst, semaphoreSecond;
public Foo() {
this.semaphoreFirst = new Semaphore(0);
this.semaphoreSecond = new Semaphore(0);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
semaphoreFirst.release();
}
public void second(Runnable printSecond) throws InterruptedException {
semaphoreFirst.acquire();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
semaphoreSecond.release();
}
public void third(Runnable printThird) throws InterruptedException {
semaphoreSecond.acquire();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
/* 解法三:CountDownLatch */
class Foo {
private CountDownLatch latchFirst, latchSecond;
public Foo() {
this.latchFirst = new CountDownLatch(1);
this.latchSecond= new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
latchFirst.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
latchFirst.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
latchSecond.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
latchSecond.await();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
交替打印FooBar
两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。
请设计修改程序,以确保 “foobar” 被输出 n 次。
示例
输入:n = 2
输出:"foobarfoobar"
解释:"foobar" 将被输出两次。
解题思路
/* 解法一:synchronized */
class FooBar {
private int n;
private Object lock;
private boolean flag;
public FooBar(int n) {
this.n = n;
this.lock = new Object();
this.flag = false;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
while (flag) {
lock.wait();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
flag = true;
lock.notifyAll();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
while (!flag) {
lock.wait();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
flag = false;
lock.notifyAll();
}
}
}
}
/* 解法二:Semaphore */
class FooBar {
private int n;
private Semaphore semaphoreFirst, semaphoreSecond;
public FooBar(int n) {
this.n = n;
this.semaphoreFirst = new Semaphore(1);
this.semaphoreSecond = new Semaphore(0);
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
semaphoreFirst.acquire();
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
semaphoreSecond.release();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
semaphoreSecond.acquire();
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
semaphoreFirst.release();
}
}
}
打印零与奇偶数
相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:
- 线程 A 将调用 zero(),它只输出 0 。
- 线程 B 将调用 even(),它只输出偶数。
- 线程 C 将调用 odd(),它只输出奇数。
每个线程都有一个 printNumber 方法来输出一个整数。请修改给出的代码以输出整数序列 010203040506… ,其中序列的长度必须为 2n。
示例
输入:n = 2
输出:"0102"
说明:三条线程异步执行,其中一个调用 zero(),另一个线程调用 even(),最后一个线程调用odd()。正确的输出为 "0102"。
解题思路
/* 解法一:synchronized */
class ZeroEvenOdd {
private int n;
private Object lock;
private boolean zeroFlag, oddFlag, evenFlag;
public ZeroEvenOdd(int n) {
this.n = n;
this.lock = new Object();
this.zeroFlag = true;
this.oddFlag = true;
this.evenFlag = false;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
while (!zeroFlag) {
lock.wait();
}
printNumber.accept(0);
zeroFlag = false;
lock.notifyAll();
}
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
for (int i = 1; i <= n; i += 2) {
synchronized (lock) {
while (zeroFlag || !oddFlag) {
lock.wait();
}
printNumber.accept(i);
zeroFlag = true;
oddFlag = false;
evenFlag = true;
lock.notifyAll();
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
for (int i = 2; i <= n; i += 2) {
synchronized (lock) {
while (zeroFlag || !evenFlag) {
lock.wait();
}
printNumber.accept(i);
zeroFlag = true;
oddFlag = true;
evenFlag = false;
lock.notifyAll();
}
}
}
}
/* 解法二:Semaphore */
class ZeroEvenOdd {
private int n;
private Semaphore semaphoreZero, semaphoreOdd, semaphoreEven;
public ZeroEvenOdd(int n) {
this.n = n;
this.semaphoreZero = new Semaphore(1);
this.semaphoreOdd = new Semaphore(0);
this.semaphoreEven = new Semaphore(0);
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for (int i = 0; i < n; i++) {
semaphoreZero.acquire();
printNumber.accept(0);
if (i % 2 == 0) {
semaphoreOdd.release();
} else {
semaphoreEven.release();
}
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
for (int i = 1; i <= n; i += 2) {
semaphoreOdd.acquire();
printNumber.accept(i);
semaphoreZero.release();
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
for (int i = 2; i <= n; i += 2) {
semaphoreEven.acquire();
printNumber.accept(i);
semaphoreZero.release();
}
}
}
H2O 生成
现在有两种线程,氢 oxygen 和氧 hydrogen,你的目标是组织这两种线程来产生水分子。存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。这些线程应该三三成组突破屏障并能立即组合产生一个水分子。你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。
换句话说:
- 如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
- 如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。
书写满足这些限制条件的氢、氧线程同步代码。
示例
输入:"OOHHHH"
输出:"HHOHHO"
解释:"HOHHHO", "OHHHHO", "HHOHOH", "HOHHOH", "OHHHOH", "HHOOHH", "HOHOHH" 和 "OHHOHH" 依然都是有效解。
解题思路
/* 解法一:synchronized */
class H2O {
private Object lock;
private int hCount;
public H2O() {
this.lock = new Object();
this.hCount = 0;
}
public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
synchronized (lock) {
while (hCount == 2) {
lock.wait();
}
// releaseHydrogen.run() outputs "H". Do not change or remove this line.
releaseHydrogen.run();
hCount++;
lock.notifyAll();
}
}
public void oxygen(Runnable releaseOxygen) throws InterruptedException {
synchronized (lock) {
while (hCount != 2) {
lock.wait();
}
// releaseOxygen.run() outputs "H". Do not change or remove this line.
releaseOxygen.run();
hCount = 0;
lock.notifyAll();
}
}
}
/* 解法二:Semaphore */
class H2O {
private Semaphore semaphoreH, semaphoreO;
public H2O() {
this.semaphoreH = new Semaphore(2);
this.semaphoreO = new Semaphore(0);
}
public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
semaphoreH.acquire();
// releaseHydrogen.run() outputs "H". Do not change or remove this line.
releaseHydrogen.run();
semaphoreO.release();
}
public void oxygen(Runnable releaseOxygen) throws InterruptedException {
semaphoreO.acquire(2);
// releaseOxygen.run() outputs "H". Do not change or remove this line.
releaseOxygen.run();
semaphoreH.release(2);
}
}