出于教育目的,我正在使用多个线程实现经典的“嘶嘶声”问题。
“嘶嘶声”游戏是:
首先要去的玩家说出数字“ 1”,此后每位玩家依次计算一个数字。但是,任何可被三整除的数字都将由fizz替换,而任何可被五整除的buzz将被替换。两者都可整除的数字变成嗡嗡声
在我的实现中,我有4个线程:
我不使用任何锁定和线程同步机制。 我的多线程“ fizz buzz”实现线程安全吗?而且,如果没有,为什么? 我在实现代码中为“可疑”位置添加了注释。
我的实现:
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”算法,启发了一本著名的书为“ Codding面试”做准备。我只是想证明书中的示例(需要有4个线程)可以在不使用同步和锁定的情况下解决。
答案 0 :(得分:3)
它不是无竞赛的(我想您真正要问的是),因为当线程currentNum
被另一个线程写入时从AtomicInteger
读取,没有任何同步。不能保证每个线程都可以看到最新的值-每个线程都可以看到其上次写入的值,或者此后其他任何线程已写入的值。
这可能意味着您最终不会在任何线程中前进,因为每个线程可能根本看不到任何其他线程所做的更改。您可以使用this.currentNum++;
来解决该问题。
我也不确定 this.currentNum++;
System.out.println(result);
的效果是否保证以与源线程中相同的方式按顺序显示给其他线程。我怀疑从理论上讲,输出和增量可以重新排序,例如:
{{1}}
这可能导致输出不一致。
答案 1 :(得分:0)
好吧,我对其进行了更改,以使其不再打印输出,而是将其发布到队列中。我让队列检查该值是否满足条件。它总是中断,因为队列已满。或者,您可以将输出写入文件并运行数千次,以查看其是否损坏。 这对我来说永远不会中断。虽然不能证明它是无种族限制的,但相反的话,这表明它是一场竞赛。
import java.util.function.IntFunction;
import java.util.concurrent.ArrayBlockingQueue;
public class FizzBuzzGameRunner {
static ArrayBlockingQueue<String> output = new ArrayBlockingQueue<>(100);
private int currentNum = 1;
public static void main(String... args) throws InterruptedException {
FizzBuzzGameRunner fizzBuzzGame = new FizzBuzzGameRunner();
startAll(
fizzBuzzGame.createRunnable(
n -> {
if (n % 3 != 0 && n % 5 != 0) {
return String.valueOf(n);
}
return null;
}),
fizzBuzzGame.createRunnable(
n -> {
if (n % 3 == 0 && n % 5 != 0) {
return "fizz";
}
return null;
}),
fizzBuzzGame.createRunnable(
n -> {
if (n % 3 != 0 && n % 5 == 0) {
return "buzz";
}
return null;
}),
fizzBuzzGame.createRunnable(
n -> {
if (n % 3 == 0 && n % 5 == 0) {
return "fizz buzz";
}
return null;
})
);
int n = 1;
String s;
while(true){
s = output.take();
if (n % 3 != 0 && n % 5 != 0) {
if(!String.valueOf(n).equals(s)){
break;
}
}
if (n % 3 == 0 && n % 5 != 0) {
if(!"fizz".equals(s)) break;
}
if (n % 3 != 0 && n % 5 == 0) {
if(!"buzz".equals(s)) break;
}
if (n % 3 == 0 && n % 5 == 0) {
if(!"fizz buzz".equals(s)) break;
}
n++;
}
System.out.println(s + " out of order after" + n);
}
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
// even if other thread updates currentNum nothing bad can happen
String result = singleStep.apply(currNum);
if (result != null) {
//even without synchronization this block will be
//executed maximum by single thread simultaneously
try{
output.put(result);
} catch(InterruptedException e){
break;
}
this.currentNum++;
}
}
};
}
}