【问题标题】:How to create a singleton class如何创建单例类
【发布时间】:2011-05-20 18:14:14
【问题描述】:

在 java 中创建单例类的最佳/正确方法是什么?

我发现的一个实现是使用私有构造函数和 getInstance() 方法。

package singleton;

public class Singleton {

    private static Singleton me;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (me == null) {
            me = new Singleton();
        }

        return me;
    }
}

但是在下面的测试用例中实现是否失败

package singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {

    /**
     * @param args
     * @throws NoSuchMethodException
     * @throws SecurityException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws IllegalArgumentException
     */
    public static void main(String[] args) throws SecurityException,
            NoSuchMethodException, IllegalArgumentException,
            InstantiationException, IllegalAccessException,
            InvocationTargetException {
        Singleton singleton1 = Singleton.getInstance();
        System.out.println(singleton1);

        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton2);

        Constructor<Singleton> c = Singleton.class
                .getDeclaredConstructor((Class<?>[]) null);
        c.setAccessible(true);
        System.out.println(c);

        Singleton singleton3 = c.newInstance((Object[]) null);
        System.out.println(singleton3);

        if(singleton1 == singleton2){
            System.out.println("Variable 1 and 2 referes same instance");
        }else{
            System.out.println("Variable 1 and 2 referes different instances");
        }
        if(singleton1 == singleton3){
            System.out.println("Variable 1 and 3 referes same instance");
        }else{
            System.out.println("Variable 1 and 3 referes different instances");
        }
    }

}

如何解决?

谢谢

【问题讨论】:

  • 根据我多年的专业经验,在 99.99% 的情况下,最好的方法是不这样做。你认为你需要一个 Singleton 来做什么,真的?
  • 首先,单身人士是邪恶的。其次,单例是全局变量,第三,不要使用单例。此外,你不能阻止反思能够对你的班级造成坏事。不要尝试!
  • 我想我会听取你的建议,如果有人使用反射来搞砸……这是他们的问题……不是我的问题。
  • @Arun,我会创建一次对象并将其传递给所有需要设置的对象的构造函数。
  • 伙计们,提出的问题是解决问题,而不是去 Dr. House 询问单人是好是坏。

标签: java design-patterns singleton


【解决方案1】:

根据您对问题的评论:

我有一个包含一些键值对的属性文件,这是整个应用程序都需要的,这就是我考虑单例类的原因。该类将从文件中加载属性并保留它,您可以在应用程序的任何位置使用它

不要使用单例。您显然不需要一次性 lazy 初始化(这就是单例的全部意义所在)。您想要一次性直接初始化。只需将其设为静态并将其加载到静态初始化程序中即可。

例如

public class Config {

    private static final Properties PROPERTIES = new Properties();

    static {
        try {
            PROPERTIES.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
        } catch (IOException e) {
            throw new ExceptionInInitializerError("Loading config file failed.", e);
        }
    }

    public static String getProperty(String key) {
        return PROPERTIES.getProperty(key);
    }

    // ...
}

【讨论】:

  • 感谢您的建议。我会尝试实现这一点。
  • 如果您是静态初始化器的新手,您可能会发现 this answer 也很有用。
  • 这仍然是一个单例。它只是一个没有延迟加载的单例。 WTH 在这里发生... ??
  • @Rob:不过,这不是大写的 S 单例。 (1) 单个实例属于不同的类型,(2) 它从不直接暴露——只有它的行为是。它仍然是相当难看的 IMO,但它至少不会假装不是离散对象。
  • @nikel:因为它不是可选的。
【解决方案2】:

如果你使用反射来穿透封装,当你的类的行为以不正确的方式改变时,你不应该感到惊讶。私有成员应该是类私有的。通过使用反射来访问它们,您故意破坏了类的行为,并且预期会产生“重复的单例”。

简而言之:不要那样做。

另外,您可以考虑在静态构造函数中创建单例实例。静态构造函数是同步的,并且只会运行一次。您当前的类包含一个竞争条件——如果两个单独的线程调用 getInstance() 而之前没有被调用,则可能会创建两个实例,其中一个是其中一个线程独有的,另一个是成为未来getInstance() 调用将返回的实例。

【讨论】:

    【解决方案3】:

    我将通过以下方式实现单例。

    来自Singleton_pattern wikiepdia 使用Initialization-on-demand holder idiom

    描述

    此解决方案是线程安全的,不需要特殊的语言结构(即volatilesynchronized

    public final class  LazySingleton {
        private LazySingleton() {}
        public static LazySingleton getInstance() {
            return LazyHolder.INSTANCE;
        }
        private static class LazyHolder {
            private static final LazySingleton INSTANCE = new LazySingleton();
        }
        private Object readResolve()  {
            return LazyHolder.INSTANCE;
        }
    }
    

    【讨论】:

      【解决方案4】:

      在 java 中创建单例类的最佳方法是使用枚举。

      示例如下:

      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.ObjectInputStream;
      import java.io.ObjectOutputStream;
      import java.io.Serializable;
      import java.lang.reflect.Constructor;
      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method; 
      
      enum SingleInstance{
          INSTANCE;
      
          private SingleInstance() {
              System.out.println("constructor");
          }   
      }
      
      public class EnumSingletonDemo {
      
          public static void main (String args[]) throws FileNotFoundException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
          {
              SingleInstance s=SingleInstance.INSTANCE;
              SingleInstance s1=SingleInstance.INSTANCE;
      
              System.out.println(s.hashCode() + " "+s1.hashCode());//prints same hashcode indicates only one instance created
      
          //------- Serialization -------
          ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("sample.ser"));
          oos.writeObject(s);
          oos.close();
      
          //------- De-Serialization -------
          ObjectInputStream ois=new ObjectInputStream(new FileInputStream("sample.ser"));
          SingleInstance s2=(SingleInstance) ois.readObject();
      
          System.out.println("Serialization :: "+s.hashCode()+" "+s2.hashCode());// prints same hashcodes because JVM handles serialization in case of enum(we dont need to override readResolve() method)
      
         //-----Accessing private enum constructor using Reflection-----
      
          Class c=Class.forName("SingleInstance");
      
          Constructor co=c.getDeclaredConstructor();//throws NoSuchMethodException
          co.setAccessible(true);
          SingleInstance newInst=(SingleInstance) co.newInstance();           
      
      }
      }
      

      NoSuchMethodException 被抛出,因为我们无法使用反射通过其私有构造函数创建枚举“SingleInstance”的另一个实例。

      在序列化的情况下,枚举默认实现可序列化的接口。

      【讨论】:

        【解决方案5】:

        我想你可以检查构造函数中是否已经存在实例,如果存在则抛出异常

        if(me != null){
            throw new InstanceAlreadyExistsException();
        }
        

        【讨论】:

        • 在私有成员中没有必要这样做,因为不应在类外部访问私有成员。如果有人想使用反射来访问私有成员,那么由此产生的行为就是他们的问题。
        【解决方案6】:
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;
        
        public class DBConnection {
        
        
            private static DBConnection dbConnection;
            private Connection connection;
        
            private DBConnection() throws ClassNotFoundException, SQLException {
                Class.forName("com.mysql.jdbc.Driver");
                connection = DriverManager.getConnection(/*crate connection*/);
            }
        
            public Connection getConnection(){
                return connection;
            }
            public static DBConnection getInstance() throws SQLException, ClassNotFoundException {
                return (null==dbConnection) ? (dbConnection = new DBConnection()) : dbConnection;
            }
        }
        
         
        

        【讨论】:

        • 您能否为您的答案添加解释?为什么使用您提供的代码有效?问题是什么?
        【解决方案7】:

        只要按照单例模式类图,

        单例类 - 单例对象:单例类 - 单例类() + getObject(): 单例类

        关键点,

        • 私有你的构造函数
        • 类的实例应该在类中
        • 提供返回实例的函数

        一些代码,

        public class SingletonClass {
            private static boolean hasObject = false;
            private static SingletonClass singletonObject = null;
        
            public static SingletonClass getObject() {
                if (hasObject) {
                    return singletonObject;
                } else {
                    hasObject = true;
                    singletonObject = new SingletonClass();
                    return singletonObject;
                }
            }
        
            private SingletonClass() {
                // Initialize your object.
            }
        }
        

        【讨论】:

        • 这段代码本质上不是线程安全的。如果两个线程同时在getObject(),则可以创建两个实例。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-24
        • 1970-01-01
        • 2015-10-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-18
        相关资源
        最近更新 更多