【发布时间】:2019-04-05 18:47:19
【问题描述】:
出于教育目的,我正在使用多线程实现经典的“嘶嘶声”问题。
“嘶嘶声”游戏是:
指定先走的玩家说出数字“1”,然后每个玩家依次数一个数字。然而,任何能被三整除的数字都被单词 fizz 代替,任何能被五整除的数字都被单词 Buzz 代替。能被两者整除的数字会变成嗡嗡声
在我的实现中,我有 4 个线程:
- 如果数字不是 3 或 5 的倍数,则第一个线程打印数字并递增当前计数器。
- 第二个线程打印“嘶嘶”...
- 第三个线程打印“嗡嗡声”...
- 第四线程打印“嘶嘶声”...
我不使用任何锁定和线程同步机制。 我的多线程“嘶嘶声”实现线程安全吗?如果不是,为什么? 我在“可疑”地方的实现代码中添加了 cmets。
我的实现:
package threads;
import java.util.function.IntFunction;
public class FizzBuzzGameRunner {
// not volatile
// if other thread updates currentNum and current thread will see old (cached) value
// nothing bad can happen, just burn some CPU cycles uselessly
private int currentNum = 1;
public static void main(String... args) throws InterruptedException {
FizzBuzzGameRunner fizzBuzzGame = new FizzBuzzGameRunner();
startAll(
fizzBuzzGame.createRunnable(n -> (n % 3 != 0 && n % 5 != 0) ? String.valueOf(n) : null),
fizzBuzzGame.createRunnable(n -> (n % 3 == 0 && n % 5 != 0) ? "fizz" : null),
fizzBuzzGame.createRunnable(n -> (n % 3 != 0 && n % 5 == 0) ? "buzz" : null),
fizzBuzzGame.createRunnable(n -> (n % 3 == 0 && n % 5 == 0) ? "fizz buzz" : null)
);
Thread.sleep(1000);
}
private static void startAll(Runnable... workers) {
for (Runnable w : workers) {
Thread t = new Thread(w);
t.setDaemon(true);
t.start();
}
}
private Runnable createRunnable(IntFunction<String> singleStep) {
return () -> {
while (true) {
int currNum = this.currentNum;
// no synchronization
String result = singleStep.apply(currNum);
if (result != null) {
//Even without synchronization this block will be
//executed maximum by single thread simultaneously.
//Because each thread increments this.currentNum as part of that action,
//but no other thread will increment for the same value.
System.out.println(result);
this.currentNum++;
}
}
};
}
}
我知道我的例子完全是人为的。实现多线程“Fizz Buzz”算法启发了一本名著,为“编码面试”做准备。我只是想证明书中的例子(需要有 4 个线程)可以在不使用同步和锁的情况下解决。
【问题讨论】:
-
@matt 我认为这个想法是(尽管问题可能需要更多解释......)只有满足其条件的线程才会增加 currNum。因此,每个线程什么都不做,直到其他线程将 currNum 提高到适当的值(在每种情况下,都会导致其适当性质的输出,“fizz”或“buzz”或“fizzbuzz”或数字)。它比最初看起来更棘手。
-
@davmac 在我的示例中,一个线程在 A 获取值和 B 获取值之间更新 curNum。例如。 currentNum = 9,A 读取该值并将 curNum 分配给 9,线程 C 更新 currentNum,tad B 分配 curNum = 10;现在 A 和 B 都有正确的值,这是一场比赛。哦,你说 A 在 C 更新之前不能得到 9,否则 C 会得到 9。
-
@matt 我不关注。 “正确的价值”是什么意思?每个线程都有一组不相交的值,它们将对其进行操作。作为该操作的一部分,每个线程都会增加 curNum,但没有其他线程会增加相同的值。在您的示例中,如果 A 将 curNum 读取为 9,然后 C 更新 curNum,那么一定是因为 C 将 curNum 视为具有它将起作用的值,这意味着 9 和 C 看到的值都不是 A 将起作用的值开,所以不管它有哪一个。
-
“正确值”是特定线程操作的值。对,所以 C 必须看到 8 并采取行动,然后将值从 9 更新为 10,如果 C 看到旧值,可能会发生这种情况。由于线程 C 是唯一将 8 修改为 9 的线程,因此它看不到陈旧的值。
-
@matt 对,我想我们现在在同一个页面上。
标签: java multithreading