【问题标题】:java:singleton, static variable and thread safetyjava:单例、静态变量和线程安全
【发布时间】:2011-03-17 13:55:55
【问题描述】:
class MyClass
{
private static MyClass obj;

public static MyClass getInstance()
{
    if(obj==null)
    {
        obj = new MyClass();
    }
    return obj;
}

在上面的java代码示例中,由于obj是类内部的静态变量, getInstance 仍然是非线程安全的吗?因为静态变量由所有线程共享,所以 2 个同时线程应该使用同一个对象。不是吗?

维普尔沙阿

【问题讨论】:

    标签: multithreading static singleton thread-safety


    【解决方案1】:

    由于静态变量被广泛共享,它们非常不安全。

    考虑一下如果两个线程同时调用您的 getInstance 会发生什么。两个线程都将查看共享静态 obj,并且两个线程都会在 if 检查中看到 obj 为空。然后两个线程都会创建一个新的obj

    你可能会想:“嘿,它是线程安全的,因为 obj 永远只有一个值,即使它被初始化了多次。”这种说法有几个问题。在我们之前的示例中,getInstance 的调用者都将获得他们自己的obj。如果两个调用者都保留对obj 的引用,那么您将使用多个单例实例。

    即使我们前面示例中的调用者只是做了:MyClass.getInstance(); 并且没有保存对 MyClass.getInstance(); 返回的内容的引用,您仍然可以最终从这些线程上的 getInstance 返回不同的实例。您甚至可以进入 obj 的新实例创建的情况,即使对 getInstance 的调用没有同时发生!

    我知道我的最后一个声明似乎违反直觉,因为对 obj 的最后一次赋值似乎是将来调用 MyClass.getInstance() 时可以返回的唯一值。但是,您需要记住,JVM 中的每个线程都有自己的主内存本地缓存。如果两个线程调用 getInstance,它们的本地缓存可能会为 obj 分配不同的值,并且将来从这些线程调用 getInstance 将返回它们缓存中的内容。

    确保 getInstance 线程安全的最简单方法是使方法同步。这将确保

    1. 两个线程不能同时进入getInstance
    2. 尝试使用obj 的线程永远不会从其缓存中获得obj 的陈旧值

    不要尝试变得聪明并使用双重检查锁定: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

    【讨论】:

      【解决方案2】:

      可以在这里找到很好的解释:

      http://en.wikipedia.org/wiki/Singleton_pattern

      该 wiki 文章重点介绍了各种线程安全方法及其优缺点。

      【讨论】:

        【解决方案3】:

        在这种情况下 getInstance() 不是线程安全的,即使您使用静态变量也是如此。只有同步才能使这个线程安全。

        【讨论】:

          【解决方案4】:

          以下示例显示了一个奇怪的线程保存修改的单吨模式,它也支持泛型。

          要让它只是线程保存和同步保存,只需使用同步块以及瞬态和易失性关键字。 请注意,有一个双重检查,同步块在 if 内。这会带来更高的性能,因为同步的成本很高。

          当然对于一个真正的单例不要使用地图,我说它是一个修改过的。

          public class Edge<T> {
              @SuppressWarnings({"unchecked"})
              private static transient volatile HashMap<Object,HashMap<Object, Edge>> instances = new HashMap<Object, HashMap<Object,Edge>>();
              /**
               * This function is used to get an Edge instance
               * @param <T> Datatype of the nodes.
               * @param node1, the source node
               * @param node2, the destination node
               * @return the edge of the two nodes.
               */
              @SuppressWarnings({"unchecked"})
              public static <T> Edge<T> getInstance(T node1, T node2){
                  if(!(instances.containsKey(node1) && instances.get(node1).containsKey(node2))){
                      synchronized (Edge.class) {
                          if(!(instances.containsKey(node1) && instances.get(node1).containsKey(node2))){
                              Edge<T> edge = new Edge<T>(node1, node2);
                              if(!instances.containsKey(node1)){
                                  instances.put(node1, new HashMap<Object, Edge>());
                              }
                              instances.get(node1).put(node2, edge);
                          }
                      }
                  }
                  return (Edge<T>)instances.get(node1).get(node2);
              }
          

          【讨论】:

          • 随机例子,双重检查锁定不起作用,如果他同步他的getInstance方法,他不需要volatile关键字,瞬态与线程安全无关。双重检查锁定被破坏:javaworld.com/javaworld/jw-02-2001/jw-0209-double.html
          • 一个修正,双重检查锁定将在使用 volatile 时起作用(在 java 5 中)。因此,如果他确实使用了双重检查锁定,那么是的,他将需要 volatile 关键字。
          • 那么为什么要投票给我呢?例子是天才。有点随机,但只需将其减少到需要的程度就可以了。如果你不知道双重检查锁定是这样工作的,没有理由抱怨。 books.google.com/…查看第182页
          【解决方案5】:
          public class Singleton{
              private static transient volatile Singleton instance;
              public static Singleton getInstance(){
                  if(instance==null)synchronized(Singleton.class){
                      if(instance==null){
                          instance = new Singleton();
                      }
                  }
                  return instance;
              }
              private Singleton(){
                  /*....*/
              }
          }
          

          第 182 页: http://books.google.com/books?id=GGpXN9SMELMC&printsec=frontcover&dq=design+patterns&hl=de&ei=EFGCTbyaIozKswbHyaiCAw&sa=X&oi=book_result&ct=result&resnum=2&ved=0CDMQ6AEwAQ#v=onepage&q&f=false

          认为现在可以将其标记为已回答。

          【讨论】:

            【解决方案6】:
            class MyClass
            {
            private static MyClass obj;
            
            private MyClass(){
                // your initialization code
            }
            public static synchronized MyClass getInstance()
            {
                if(obj==null)
                {
                    obj = new MyClass();
                }
                return obj;
            }
            

            我同意@Manoj。 我相信以上将是实现单例对象的最佳方法之一。 同步使对象线程安全。 甚至,它是静态的:)

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-11-18
              • 2014-09-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多