[LeetCode] 1115. 交替打印FooBar(Medium)Java语言题解

1.题目相关

①题目及示例

Alt text
Alt text

②相关标签

  • 多线程

③题目地址


2.解题方法

①synchronized 关键字 + this 锁对象

  • 原因:两个线程属于同一个实例,虽然两个线程访问的是一个类的不同的普通同步方法,但是两个普通同步方法默认都是以 this 对象作为同步方法的锁,所以它们会争抢同一把锁(对于同一个实例来讲,两个方法的 this 对象是同一个)。
  • 结果:两个线程争抢同一把锁,同一时刻只能有一个线程执行该线程对应的同步方法,然后再使用 wait 方法和 notify 方法,使得两个线程交替运行。

②lock + condition

  • lock 代替 synchronized 关键字
  • condition 中的 await 方法和 release 方法代替 Object 对象的 wait 方法和 notify 方法

③信号量

  • 两个线程第一次执行时,因为 bar 信号量的计数器初始值为 0,所以需要等 foo 线程中释放了 bar 信号量,bar 线程才能开始执行。而 foo 信号量在获取了一个许可后,其计数器值也变为 0,所以 foo 线程在下一次循环开始时需要等 bar 线程中释放了 foo 信号量,foo 线程才能继续执行。

④CountDownLatch + CyclicBarrier

  • CyclicBarrier 用于保证任务按组循环执行
  • CounDownLatch 用于保证一个循环内线程执行的先后顺序
  • 具体实现见代码

3.代码详解

①synchronized 关键字 + this 锁对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 1.synchronized关键字+this锁对象(两个不同的线程将会共用一个FooBar实例)
class FooBar {
private int n;

public FooBar(int n) {
this.n = n;
}

public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (this) {
printFoo.run();
// 唤醒另一个线程
this.notify();
// 自己陷入等待
this.wait();
}
}
}

public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (this) {
printBar.run();
// 唤醒另一个线程
this.notify();
// 防止线程在最后一次打印Bar时睡眠,从而造成死锁。
if (i != n - 1) {
// 自己陷入等待
this.wait();
}
}
}
}
}

②lock + condition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 2.lock+condition
class FooBar {
private int n;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public FooBar(int n) {
this.n = n;
}

public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
printFoo.run();
// 唤醒另一个线程
condition.signalAll();
// 自己陷入等待
condition.await();
lock.unlock();
}
}

public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
printBar.run();
// 唤醒另一个线程
condition.signalAll();
// 防止线程在最后一次打印Bar时睡眠,从而造成死锁。
if (i != n - 1) {
// 自己陷入等待
condition.await();
}
lock.unlock();
}
}
}

③信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 3.信号量(Semaphore)
class FooBar {
private int n;
private Semaphore foo = new Semaphore(1);
private Semaphore bar = new Semaphore(0);

public FooBar(int n) {
this.n = n;
}

public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// 获取foo信号量的一个许可
foo.acquire();
printFoo.run();
// 释放bar信号量的一个许可
bar.release();
}
}

public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// 获取bar信号量的一个许可
bar.acquire();
printBar.run();
// 释放foo信号量的一个许可
foo.release();
}
}
}

④CountDownLatch + CyclicBarrier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 4.CountDownLatch+CyclicBarrier
class FooBar {
private int n;
// CyclicBarrier用于保证任务按组循环执行
private CyclicBarrier barrier = new CyclicBarrier(2);
// CounDownLatch用于保证一个循环内线程执行的先后顺序
private CountDownLatch latch = new CountDownLatch(1);

public FooBar(int n) {
this.n = n;
}

public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
try{
printFoo.run();
// 触发bar线程执行
latch.countDown();
// 等待bar线程执行完成
barrier.await();
} catch(Exception e) {}
}
}

public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
try {
// 等待被触发
latch.await();
printBar.run();
latch = new CountDownLatch(1);
// 触发foo线程和bar线程继续执行(进行下一次循环)
barrier.await();
} catch (Exception e) {}
}
}
}

附录

0%