【问题标题】:Other Ways of Singleton in Java [duplicate]Java中Singleton的其他方式[重复]
【发布时间】:2011-01-07 21:07:05
【问题描述】:

只是我在考虑编写单例类的其他方法。那么这个类是否被视为单例类?

      public class MyClass{
            static Myclass myclass;

            static { myclass = new MyClass();}

            private MyClass(){}

            public static MyClass getInstance()
            { 
                return myclass;
            }
       }

因为静态块只运行一次。

【问题讨论】:

  • 这相当于static Myclass myclass = new MyClass()
  • 更好的问题,我们可以在一个线程中收集多少个单例模式的损坏/错误实现?

标签: java design-patterns singleton


【解决方案1】:

不,不是。你没有声明myClassprivate static finalgetInstance() 也不是static。代码也没有真正编译。

这是Singleton 成语:

public class MyClass {
    private static final MyClass myClass = new MyClass();

    private MyClass() {}

    public static MyClass getInstance() {
        return myClass; 
    }
}

它应该是private,这样其他人就不能直接访问它。它应该是static,所以只有一个。它应该是final,这样它就不能被重新分配。您还需要在声明期间直接实例化它,这样您就不必(那么)担心线程。

如果加载成本很高,因此您更喜欢单例的延迟加载,那么请考虑 Singleton holder 习惯用法,它根据需要而不是在类加载期间进行初始化:

public class MyClass {
    private MyClass() {}

    private static class LazyHolder {
        private static final MyClass myClass = new MyClass();
    }

    public static MyClass getInstance() {
        return LazyHolder.myClass;
    }
}

然而,你应该在是否需要Singleton 时打上大大的问号。通常不需要它。 只是一个静态变量、一个枚举、一个工厂类和/或依赖注入通常是更好的选择。

【讨论】:

【解决方案2】:

这是另一种方法:

public enum Singleton{
  INSTANCE("xyz", 123);

  // Attributes
  private String str;
  private int i;

  // Constructor
  Singleton(String str, int i){
    this.str = str;
    this.i = i;
  }
}

根据 Josh Bloch 的 Effective Java,这是在 Java 中实现 Singleton 的最佳方式。与涉及私有静态实例字段的实现不同,可以通过滥用反射和/或序列化来多次实例化,枚举保证是单例。

枚举单例的主要限制是它们总是在类加载时被实例化并且不能被延迟实例化。因此,例如,如果您想使用运行时参数实例化单例,则必须使用不同的实现(最好使用双重检查锁定)。

【讨论】:

  • 同意,这个实例将是类加载器级别的单例。
  • 同意(除非您想要延迟初始化),但为了未来的读者,解释一下为什么它是“最佳”方式会很有用。 Bloch 说它“为多次实例化提供了铁定的保证,即使面对复杂的序列化或反射攻击也是如此”。
  • @KlitosKyriacou,请随时修改我的答案。
  • @missingfaktor 谢谢,我刚刚在最后加了一句。
【解决方案3】:

在 java 中创建单例有 3 种方法。

  1. 渴望初始化单例

    public class Test {
        private static final Test TEST = new Test();
    
        private Test() {
        }
    
        public static Test getTest() {
            return TEST;
        }
    }
    
  2. 惰性初始化单例(线程安全)

    public class Test {
        private static volatile Test test;
        private Test(){}
        public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();}
                }
            }
            return test;
        }
    }
    
  3. Bill Pugh Singleton with Holder Pattern(最好是最好的)

    public class Test {
        private Test(){}
    
        private static class TestHolder {
            private static final Test TEST = new Test();
        }
    
        public static Test getInstance() {
            return TestHolder.TEST;
        }
    }
    

【讨论】:

    【解决方案4】:

    使用您的示例并使用 GoF 的实现方式:

    public class MyClass{
        private static Myclass instance;
    
        private MyClass(){
            //Private instantiation
        }
    
        public static synchronized MyClass getInstance()  //If you want your method thread safe...
        { 
            if (instance == null) {
                instance = new MyClass();
            }
    
            return instance;
        }
    }
    

    希望这会有所帮助:

    【讨论】:

    • 应该是“返回实例”
    • 谢谢...我更新了...谢谢你看到我的错误。
    【解决方案5】:

    这是我的做法。它更快,因为它在创建实例时只需要一个synchronized 块。

    public class MyClass
    {
        private static MyClass INSTANCE=null;
    
        private MyClass()
        {
        }
    
        public static MyClass getInstance()
        {
            if(INSTANCE==null)
            {
                synchronized(MyClass.class)
                {
                    if(INSATCNE==null) INSTANCE=new MyClass();
                }
            }
            return INSTANCE;
        }
    }
    

    【讨论】:

    • 双重检查锁定被破坏。
    • 要修复 J2SE 5 及更高版本中的双重检查锁定习惯用法,请将您的变量设置为 volatile,例如私有静态 volatile MyClass INSTANCE=null;
    【解决方案6】:

    你的班级(原始代码,编辑前):

    public class MyClass {
        Myclass myclass;
    
        static { myclass = new MyClass();}
    
        private MyClass(){}
    
        public MyClass getInstance()
        {
            return myclass;
        }
    }
    

    不是真正的单例:

    1. myclass 字段不是私有的,可以从外部读取和更改(假设您有一个实例来执行此操作
    2. 字段myclass不是静态的,不能在静态构造函数中访问(编译错误)
    3. getInstance() 方法不是静态的,所以你需要一个实例来调用它


    实际代码:

    public class MyClass {
        static Myclass myclass;
    
        static { myclass = new MyClass();}
    
        private MyClass(){}
    
        public static MyClass getInstance()
        {
            return myclass;
        }
    }
    

    仍然有 myclass 不是私有的(也不是最终的)...声明它是最终的将有助于防止它在类内部被无意更改。

    private static final Myclass myclass;
    

    【讨论】:

      【解决方案7】:
      public class singletonPattern {
          private static singletonPattern obj;
      
          public static singletonPattern getObject() {
              return obj = (obj == null) ? new singletonPattern() : obj;
          }
      
          public static void main(String args[]) {
              singletonPattern sng = singletonPattern.getObject();
          }
      }
      

      【讨论】:

      • 单例声明非常简单。
      • 你不应该把类的构造函数也设为私有吗?避免通过它实例化类?
      • @Allan 如果您将类的构造设为私有。无法访问此构造函数。
      • 在 Singleton 类中是可能的吗?并且您不想让其他类使用该构造函数来实例化您的类吗?
      • @Allan 此代码中没有构造函数。我不知道你为什么要这么坚持。
      【解决方案8】:

      在这方面可能有点晚了,但基本实现看起来像这样:

      public class MySingleton {
      
           private static MySingleton INSTANCE;
      
           public static MySingleton getInstance() {
              if (INSTANCE == null) {
                  INSTANCE = new MySingleton();
              }
      
              return INSTANCE;
          }
          ...
      }
      

      这里我们有一个 MySingleton 类,它有一个名为 INSTANCE 的私有静态成员和一个名为 getInstance() 的公共静态方法。第一次调用 getInstance() 时,INSTANCE 成员为空。然后流程将落入创建条件并创建 MySingleton 类的新实例。对 getInstance() 的后续调用将发现 INSTANCE 变量已设置,因此不会创建另一个 MySingleton 实例。这确保只有一个 MySingleton 实例在 getInstance() 的所有调用者之间共享。

      但是这个实现有一个问题。多线程应用程序在创建单个实例时会有竞争条件。如果多个执行线程同时(或大约)同时调用 getInstance() 方法,它们都会将 INSTANCE 成员视为 null。这将导致每个线程创建一个新的 MySingleton 实例并随后设置 INSTANCE 成员。


      private static MySingleton INSTANCE;
      
      public static synchronized MySingleton getInstance() {
          if (INSTANCE == null) {
              INSTANCE = new MySingleton();
          }
      
          return INSTANCE;
      }
      

      这里我们使用方法签名中的 synchronized 关键字来同步 getInstance() 方法。这肯定会解决我们的比赛条件。线程现在将阻塞并一次进入一个方法。但它也会产生性能问题。此实现不仅同步单个实例的创建,还同步所有对 getInstance() 的调用,包括读取。读取不需要同步,因为它们只是返回 INSTANCE 的值。由于读取将构成我们调用的大部分(请记住,实例化仅在第一次调用时发生),因此同步整个方法会导致不必要的性能损失。


      private static MySingleton INSTANCE;
      
      public static MySingleton getInstance() {
          if (INSTANCE == null) {
              synchronize(MySingleton.class) {
                  INSTANCE = new MySingleton();
              }
          }
      
          return INSTANCE;
      }
      

      在这里,我们将同步从方法签名移到了一个同步块,该块包装了 MySingleton 实例的创建。但这能解决我们的问题吗?好吧,我们不再阻止读取,但我们也向后退了一步。多个线程将同时或大约同时访问 getInstance() 方法,它们都将 INSTANCE 成员视为 null。然后,他们将点击同步块,其中一个人将获得锁并创建实例。当该线程退出该块时,其他线程将争夺锁,每个线程将一个接一个地穿过该块并创建我们类的新实例。所以我们又回到了开始的地方。


      private static MySingleton INSTANCE;
      
      public static MySingleton getInstance() {
          if (INSTANCE == null) {
              synchronized(MySingleton.class) {
                  if (INSTANCE == null) {
                      INSTANCE = createInstance();
                  }
              }
          }
      
          return INSTANCE;
      }
      

      在这里,我们从 INSIDE 块发出另一个检查。如果已经设置了 INSTANCE 成员,我们将跳过初始化。这称为双重检查锁定。

      这解决了我们的多重实例化问题。但是,我们的解决方案再一次提出了另一个挑战。其他线程可能不会“看到” INSTANCE 成员已更新。这是因为 Java 如何优化内存操作。线程将变量的原始值从主存复制到 CPU 的缓存中。然后将值的更改写入该缓存并从该缓存中读取。这是 Java 的一个特性,旨在优化性能。但这给我们的单例实现带来了问题。第二个线程 — 由不同的 CPU 或内核处理,使用不同的缓存 — 不会看到第一个线程所做的更改。这将导致第二个线程将 INSTANCE 成员视为 null,从而强制创建我们的单例的新实例。


      private static volatile MySingleton INSTANCE;
      
      public static MySingleton getInstance() {
          if (INSTANCE == null) {
              synchronized(MySingleton.class) {
                  if (INSTANCE == null) {
                      INSTANCE = createInstance();
                  }
              }
          }
      
          return INSTANCE;
      }
      

      我们通过在 INSTANCE 成员的声明中使用 volatile 关键字来解决这个问题。这将告诉编译器始终读取和写入主内存,而不是 CPU 缓存。

      但这种简单的改变是有代价的。因为我们绕过了 CPU 缓存,所以每次操作 volatile INSTANCE 成员时都会对性能造成影响 — 我们做了 4 次。我们再次检查存在(1 和 2),设置值(3),然后返回值(4)。有人可能会说这条路径是边缘情况,因为我们只在第一次调用该方法时创建实例。也许对创作的影响是可以容忍的。但即使是我们的主要用例 reads 也会对 volatile 成员进行两次操作。一次检查存在,再次返回其值。


      private static volatile MySingleton INSTANCE;
      
      public static MySingleton getInstance() {
          MySingleton result = INSTANCE;
          if (result == null) {
              synchronized(MySingleton.class) {
                  result = INSTANCE;
                  if (result == null) {
                      INSTANCE = result = createInstance();
                  }
              }
          }
      
          return result;
      }
      

      由于性能损失是由于直接对 volatile 成员进行操作,让我们将一个局部变量设置为 volatile 的值,并改为对局部变量进行操作。这将减少我们对 volatile 进行操作的次数,从而收回我们失去的一些性能。请注意,当我们进入同步块时,我们必须再次设置我们的局部变量。这样可以确保在我们等待锁定时发生的任何更改都是最新的。

      我最近写了一篇关于这个的文章。 Deconstructing The Singleton。您可以在此处找到有关这些示例的更多信息以及“持有人”模式的示例。还有一个真实的例子展示了双重检查的易失性方法。希望这会有所帮助。

      【讨论】:

        【解决方案9】:

        Singloton 类是您每次都获得相同对象的类。 当你想限制一个类创建多个对象时,我们需要单例类。

        例如:

        public class Booking {
            static Booking b = new Booking();
            private Booking() { }
            static Booking createObject() { return b; }
        }
        

        要创建这个类的对象,我们可以使用:

        Booking b1, b2, b3, b4;
        b1 = Booking.createObject();
        b2 = Booking.createObject();
        Booking b1, b2, b3, b4;
        b1 = Booking.createObject();
        b2 = Booking.createObject();
        

        b1b2 指的是同一个对象。

        【讨论】:

          【解决方案10】:

          在创建单例类时应该考虑以下属性

          1. 反射
          2. 多线程
          3. 克隆
          4. 序列化

          如果你的类中没有克隆或序列化接口,我认为以下类最好作为单例类。

          public class JavaClass1 {
          private static JavaClass1 instance = null;
          private JavaClass1() {
              System.out.println("Creating -------");
              if (instance != null) { // For Reflection
                  throw new RuntimeException("Cannot create, please use getInstance()");
              }
          }
          
          public static JavaClass1 getInstance() {
              if (instance == null) {
                  createInstance();
              }
              return instance;
          }
           private static synchronized void createInstance() { // for multithreading
              if (instance == null) {
                  instance = new JavaClass1();
              }
          }}
          

          【讨论】:

            猜你喜欢
            • 2015-02-19
            • 2016-06-17
            • 2012-07-27
            • 1970-01-01
            • 2015-12-17
            • 2014-11-15
            • 1970-01-01
            • 2019-01-19
            • 2020-02-04
            相关资源
            最近更新 更多