我的多线程“ fizz buzz”实现线程安全吗?

时间:2018-11-01 14:13:51

标签: java multithreading

出于教育目的,我正在使用多个线程实现经典的“嘶嘶声”问题。

“嘶嘶声”游戏是:

  

首先要去的玩家说出数字“ 1”,此后每位玩家依次计算一个数字。但是,任何可被三整除的数字都将由fizz替换,而任何可被五整除的buzz将被替换。两者都可整除的数字变成嗡嗡声

在我的实现中,我有4个线程:

  • 如果第一个线程不是3或5的倍数,则第一个线程将打印数字,并递增当前计数器。
  • 第二个线程显示“嘶嘶声” ...
  • 第三线程打印“嗡嗡声” ...
  • Forth线程打印“嘶嘶声” ...

我不使用任何锁定和线程同步机制。 我的多线程“ 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个线程)可以在不使用同步和锁定的情况下解决。

2 个答案:

答案 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++;
                }
            }
        };
    }



}