代码中的奇怪行为,用于测试线程安全的单例

时间:2015-12-03 14:43:35

标签: java multithreading enums singleton

我对Java中的多线程很新。因为我需要一个线程安全的单例(我实现为枚举),我写了一个小的测试代码,它会产生一个奇怪的输出。

守则:

rsync -ar --include="*/" --include="*.txt" --exclude="*" username@hostname:/home/user/dir1(dir2,dirHello) destination_dir

所以每个线程都在属性" message"中写入它的名字。然后将枚举打印到STDOUT。我得到以下输出,我觉得很奇怪:

public enum EnumSingleton {
    INSTANCE;


    /** state variables */
    private String message;

    /** Constructor */
    private EnumSingleton() {
    }

    /** add well-known accessor for the instance  (is NOT necessary) */
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }


    /** Accessors */
    public String getMessage() {
        return message;
    }

    public void setMessage(String name) {
        this.message = name;
    }
}

public class App {

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            final int b = i;
            Thread thread = new Thread("Thread #" + b) {
                @Override
                public void run() {
                    EnumSingleton singleton = EnumSingleton.getInstance();
                    singleton.setMessage("Message written by "+this.getName());
                    System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage());
                }
            };
            thread.start();
        }
    }
}

我期望的是我得到每个循环计数器(0-9)一条消息。但是在这个例子中我有多个由Thread#3写的消息,那怎么可能呢?有竞争条件吗?

如果我的代码是废话:我如何正确测试我的单例以获得线程安全?

4 个答案:

答案 0 :(得分:6)

这里有一个明确的竞争条件,因为你在枚举的单例实例中有一个变量message。你的线程都在同时写入和读取该变量,所以你希望看到这样的结果。

enum构造意味着单例对象的 creation 是线程安全的,但是仍然需要正确处理对其中方法的调用。

执行您要找的工作的方法是让message成为thread local变量,或者将消息的设置及其读取放在一个synchronized内阻止,可能锁定单例对象。

答案 1 :(得分:5)

你的单身人士不是线程安全的 - 你没有做任何事情来保证message变量的可见性。

你可以为此volatile。但请注意,输出可能有很多不同之处 - 特别是对于每个Message written by Thread #i,您不一定会得到一个i

答案 2 :(得分:4)

发生的事情是:

  • 主题#7 singleton.setMessage("Message written by #7");
  • 主题#2 singleton.setMessage("Message written by #2");
  • 主题#0 singleton.setMessage("Message written by #0");
  • 主题#3 singleton.setMessage("Message written by #3");
  • 主题#7 System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
  • 主题#2 System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
  • 主题#0 System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
  • 主题#3 System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())

答案 3 :(得分:2)

你混合了两件不同的东西。一个是对象是否是单例以及对象是否是可变/不可变的。在你的情况下,你有一个单例对象,但它是可变的。

这意味着线程确实获得了相同的对象实例并改变了EnumSingleton对象的状态。

在您的情况下,您希望使对象不可变或执行以下操作:

private static final Object lock = new Object();

public static void main(String[] args) {

    for (int i = 0; i < 10; i++) {
        final int b = i;
        Thread thread = new Thread("Thread #" + b) {
            @Override
            public void run() {
                synchronized (lock) {
                     EnumSingleton singleton = EnumSingleton.getInstance();
                     singleton.setMessage("Message written by "+this.getName());
                     System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage());
                }

            }
        };
        thread.start();
    }
}

上面锁定对象的重点是使设置和获取消息的操作成为原子操作。只需同步setMessage()getMessage()方法,您就无法逃脱。