【问题标题】:Strange behavior in a code to test a thread-safe singleton测试线程安全单例的代码中的奇怪行为
【发布时间】:2015-12-03 14:43:34
【问题描述】:

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

代码:

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();
        }
    }
}

因此,每个线程都将其名称写入枚举的属性“消息”中,然后将其打印到 STDOUT。我得到以下我觉得奇怪的输出:

Current thread Thread #6: Message written by Thread #3
Current thread Thread #1: Message written by Thread #1
Current thread Thread #8: Message written by Thread #8
Current thread Thread #5: Message written by Thread #1
Current thread Thread #4: Message written by Thread #4
Current thread Thread #9: Message written by Thread #9
Current thread Thread #7: Message written by Thread #3
Current thread Thread #2: Message written by Thread #3
Current thread Thread #0: Message written by Thread #3
Current thread Thread #3: Message written by Thread #3

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

如果我的代码很垃圾:如何正确测试我的单例的线程安全性?

【问题讨论】:

  • 枚举已经是单例了,你还需要什么?
  • @Stultuske :线程安全和可测试性,但首先,我想了解“它”

标签: java multithreading enums singleton


【解决方案1】:

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

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

做你正在寻找的方法是让message成为一个thread local变量,或者将消息的设置和它的读取放在一个synchronized块中,可能锁定在单例对象。

【讨论】:

  • 你对他们“寻找”的东西做了很多假设。 ThreadLocal 可以使他的学习测试产生预期结果这一事实并不意味着ThreadLocal 将有助于解决促使他编写测试的实际问题。我提到这一点是因为ThreadLocal 是我在生产代码中很少看到的东西,当我看到它时,代码通常会做一些过于复杂的事情,无法委托给刚刚学习使用线程的人。
【解决方案2】:

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

您可以为此设置volatile。但请注意,输出可能是许多不同的东西 - 特别是您不一定会为每个 i 获得一个 Message written by Thread #i

【讨论】:

    【解决方案3】:

    正在发生的事情是:

    • 线程 #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())

    【讨论】:

      【解决方案4】:

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

      这意味着线程确实获得了相同的对象实例并改变了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() 方法同步是无法逃脱的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-23
        • 1970-01-01
        • 2012-12-04
        • 2017-03-16
        • 2011-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多