此文首發於我的Jekyll博客:zhang0peter的個人博客
LeetCode題解文章分類:LeetCode題解文章集合
LeetCode 所有題目總結:LeetCode 所有題目總結
題目地址:Fizz Buzz Multithreaded - LeetCode
Write a program that outputs the string representation of numbers from 1 to n, however:
If the number is divisible by 3, output “fizz”.
If the number is divisible by 5, output “buzz”.
If the number is divisible by both 3 and 5, output “fizzbuzz”.
For example, for n = 15, we output: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz.
Suppose you are given the following code:
class FizzBuzz {
public FizzBuzz(int n) { ... } // constructor
public void fizz(printFizz) { ... } // only output "fizz"
public void buzz(printBuzz) { ... } // only output "buzz"
public void fizzbuzz(printFizzBuzz) { ... } // only output "fizzbuzz"
public void number(printNumber) { ... } // only output the numbers
}
Implement a multithreaded version of FizzBuzz with four threads. The same instance of FizzBuzz will be passed to four different threads:
Thread A will call fizz() to check for divisibility of 3 and outputs fizz.
Thread B will call buzz() to check for divisibility of 5 and outputs buzz.
Thread C will call fizzbuzz() to check for divisibility of 3 and 5 and outputs fizzbuzz.
Thread D will call number() which should only output the numbers.
這道題目是LeetCode的併發系列題目,題目意思也很簡單,4個線程分別做不同的事情,難的點是需要4個線程都完成任務後都進行等待。
總結一下任務過程:
1.調用4個函數,然後等待
2.4個函數調用都完成後,n+1.
3.如果一個函數重複調用,可以選擇阻塞線程,或者直接跳過。
既然提到多線程的任務同步,很容易想到Java線程庫中的CountDownLatch
和CyclicBarrier
。
先介紹一下:
CountDownLatch 主要用來解決一個線程等待多個線程的場景,可以類比旅遊團團長要等待所有的遊客到齊才能去下一個景點;而 CyclicBarrier 是一組線程之間互相等待,更像是幾個驢友之間不離不棄。除此之外 CountDownLatch 的計數器是不能循環利用的,也就是說一旦計數器減到 0,再有線程調用 await(),該線程會直接通過。但 CyclicBarrier 的計數器是可以循環利用的,而且具備自動重置的功能,一旦計數器減到 0 會自動重置到你設置的初始值。除此之外,CyclicBarrier 還可以設置回調函數,可以說是功能豐富。
但這道題目的難點在於4個線程要相互等待,而且一個函數可能被重複調用,等4個函數都執行完成後才能對n進行操作。
在思考後我覺得還是一個函數直接完成循環知道完成任務更容易實現。
Java解法如下:
class FizzBuzz {
private int n;
private int currentNumber = 1;
private final Object mutex = new Object();
public FizzBuzz(int n) {
this.n = n;
}
// printFizz.run() outputs "fizz".
public void fizz(Runnable printFizz) throws InterruptedException {
synchronized (mutex) {
while (currentNumber <= n) {
if (currentNumber % 3 != 0 || currentNumber % 5 == 0) {
mutex.wait();
continue;
}
printFizz.run();
currentNumber += 1;
mutex.notifyAll();
}
}
}
// printBuzz.run() outputs "buzz".
public void buzz(Runnable printBuzz) throws InterruptedException {
synchronized (mutex) {
while (currentNumber <= n) {
if (currentNumber % 5 != 0 || currentNumber % 3 == 0) {
mutex.wait();
continue;
}
printBuzz.run();
currentNumber += 1;
mutex.notifyAll();
}
}
}
// printFizzBuzz.run() outputs "fizzbuzz".
public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
synchronized (mutex) {
while (currentNumber <= n) {
if (currentNumber % 15 != 0) {
mutex.wait();
continue;
}
printFizzBuzz.run();
currentNumber += 1;
mutex.notifyAll();
}
}
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void number(IntConsumer printNumber) throws InterruptedException {
synchronized (mutex) {
while (currentNumber <= n) {
if (currentNumber % 3 == 0 || currentNumber % 5 == 0) {
mutex.wait();
continue;
}
printNumber.accept(currentNumber);
currentNumber += 1;
mutex.notifyAll();
}
}
}
}
這裏用了鎖解決併發問題,但如果想要不用鎖,循環輪詢等待的話,可以使用AtomicInteger
。