草庐IT

java - 现在更好的 Java 单例模式?

coder 2023-08-31 原文

这个问题在这里已经有了答案:





What is an efficient way to implement a singleton pattern in Java? [closed]

(29 个回答)


5年前关闭。




您知道,自从 Java 5 发布以来,在 Java 中编写单例模式的推荐方法是使用枚举。

public enum Singleton {
    INSTANCE;
}

但是,我不喜欢这样做 - 强制客户端使用 Singleton.INSTANCE 以便访问单例实例。
也许,将单例隐藏在普通类中的更好方法,并提供对单例设施的更好访问:
public class ApplicationSingleton {
    private static enum Singleton {
        INSTANCE;               

        private ResourceBundle bundle;

        private Singleton() {
            System.out.println("Singleton instance is created: " + 
            System.currentTimeMillis());

            bundle = ResourceBundle.getBundle("application");
        }

        private ResourceBundle getResourceBundle() {
            return bundle;
        }

        private String getResourceAsString(String name) {
            return bundle.getString(name);
        }
    };

    private ApplicationSingleton() {}

    public static ResourceBundle getResourceBundle() {
        return Singleton.INSTANCE.getResourceBundle();
    }

    public static String getResourceAsString(String name) {
        return Singleton.INSTANCE.getResourceAsString(name);
    }
}

因此,客户端现在可以简单地编写:
ApplicationSingleton.getResourceAsString("application.name")

例如。
哪个好得多:
Singleton.INSTANCE.getResourceAsString("application.name")

所以,问题是:这是正确的做法吗?这段代码是否有任何问题(线程安全?)?它是否具有“枚举单例”模式的所有优点?似乎它需要从两个世界中取得更好的成绩。你怎么认为?有没有更好的方法来实现这一目标?
谢谢。

编辑
@全部
在 Effective Java,第 2 版中首先提到了单例模式的枚举用法:wikipedia:Java Enum Singleton .我完全同意我们应该尽可能减少 Singleton 的使用,但我们不能完全摆脱它们。
在我提供另一个示例之前,让我说,带有 ResourceBundle 的第一个示例只是一个案例,示例本身(和类名称)并非来自实际应用程序。但是,需要说的是,我不知道 ResourceBundle 缓存管理,感谢您提供的信息)

下面,单例模式有两种不同的方法,第一种是使用 Enum 的新方法,第二种是我们大多数人以前使用的标准方法。我试图展示它们之间的显着差异。

使用枚举的单例:
ApplicationSingleton 类是:
public class ApplicationSingleton implements Serializable {
    private static enum Singleton {
        INSTANCE;               

        private Registry registry;

        private Singleton() {
            long currentTime = System.currentTimeMillis(); 
            System.out.println("Singleton instance is created: " + 
                    currentTime);

            registry = new Registry(currentTime);
        }

        private Registry getRegistry() {
            return registry;
        }

        private long getInitializedTime() {
            return registry.getInitializedTime();
        }

        private List<Registry.Data> getData() {
            return registry.getData();
        }
    };

    private ApplicationSingleton() {}

    public static Registry getRegistry() {
        return Singleton.INSTANCE.getRegistry();
    }

    public static long getInitializedTime() {
        return Singleton.INSTANCE.getInitializedTime();
    }

    public static List<Registry.Data> getData() {
        return Singleton.INSTANCE.getData();
    }    
}

注册表类是:
public class Registry {
    private List<Data> data = new ArrayList<Data>();
    private long initializedTime;

    public Registry(long initializedTime) {
        this.initializedTime = initializedTime;
        data.add(new Data("hello"));
        data.add(new Data("world"));
    }

    public long getInitializedTime() {
        return initializedTime;
    }

    public List<Data> getData() {
        return data;
    }

    public class Data {      
        private String name;

        public Data(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }                   
    }
}

和测试类:
public class ApplicationSingletonTest {     

    public static void main(String[] args) throws Exception {                   

        String rAddress1 = 
            ApplicationSingleton.getRegistry().toString();

        Constructor<ApplicationSingleton> c = 
            ApplicationSingleton.class.getDeclaredConstructor();
        c.setAccessible(true);
        ApplicationSingleton applSingleton1 = c.newInstance();
        String rAddress2 = applSingleton1.getRegistry().toString();

        ApplicationSingleton applSingleton2 = c.newInstance();
        String rAddress3 = applSingleton2.getRegistry().toString();             


        // serialization

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(applSingleton1);

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
        ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();

        String rAddress4 = applSingleton3.getRegistry().toString();

        List<Registry.Data> data = ApplicationSingleton.getData();
        List<Registry.Data> data1 = applSingleton1.getData();
        List<Registry.Data> data2 = applSingleton2.getData();
        List<Registry.Data> data3 = applSingleton3.getData();

        System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
        System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
        System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
        System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
                ApplicationSingleton.getInitializedTime(),
                applSingleton1.getInitializedTime(), 
                applSingleton2.getInitializedTime(),
                applSingleton3.getInitializedTime());
    }

}

这是输出:
Singleton instance is created: 1304067070250
applSingleton1=ApplicationSingleton@18a7efd, applSingleton2=ApplicationSingleton@e3b895, applSingleton3=ApplicationSingleton@6b7920
rAddr1=Registry@1e5e2c3, rAddr2=Registry@1e5e2c3, rAddr3=Registry@1e5e2c3, rAddr4=Registry@1e5e2c3
dAddr1=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr2=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr3=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr4=[Registry$Data@1dd46f7, Registry$Data@5e3974]
time0=1304067070250, time1=1304067070250, time2=1304067070250, time3=1304067070250

应该提到的是:
  • 单例实例仅创建一次
  • 是的,ApplicationSingletion 有几个不同的实例,但它们都包含相同的 Singleton 实例
  • 所有 的注册表内部数据都相同不同 ApplicationSingleton 实例

  • 所以,总结一下:枚举方法工作正常,防止通过反射攻击重复创建单例,并在序列化后返回相同的实例。

    单例使用标准方法:

    ApplicationSingleton 类是:
    public class ApplicationSingleton implements Serializable {
        private static ApplicationSingleton INSTANCE;
    
        private Registry registry;
    
        private ApplicationSingleton() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {}        
            long currentTime = System.currentTimeMillis();
            System.out.println("Singleton instance is created: " + 
                    currentTime);
            registry = new Registry(currentTime);
        }
    
        public static ApplicationSingleton getInstance() {
            if (INSTANCE == null) {
                return newInstance();
            }
            return INSTANCE;
    
        }
    
        private synchronized static ApplicationSingleton newInstance() {
            if (INSTANCE != null) {
                return INSTANCE;
            }
            ApplicationSingleton instance = new ApplicationSingleton();
            INSTANCE = instance;
    
            return INSTANCE;
        }
    
        public Registry getRegistry() {
            return registry;
        }
    
        public long getInitializedTime() {
            return registry.getInitializedTime();
        }
    
        public List<Registry.Data> getData() {
            return registry.getData();
        }
    }
    

    Registry 类是(注意 Registry 和 Data 类应该明确地实现 Serializable 以便序列化工作):
    //now Registry should be Serializable in order serialization to work!!!
    public class Registry implements Serializable {
        private List<Data> data = new ArrayList<Data>();
        private long initializedTime;
    
        public Registry(long initializedTime) {
            this.initializedTime = initializedTime;
            data.add(new Data("hello"));
            data.add(new Data("world"));
        }
    
        public long getInitializedTime() {
            return initializedTime;
        }
    
        public List<Data> getData() {
            return data;
        }
    
        // now Data should be Serializable in order serialization to work!!!
        public class Data implements Serializable {      
            private String name;
    
            public Data(String name) {
                this.name = name;
            }
    
            public String getName() {
                return name;
            }                   
        }
    }
    

    ApplicationSingletionTest 类是(大体相同):
    public class ApplicationSingletonTest {     
    
        public static void main(String[] args) throws Exception {
    
            String rAddress1 = 
                ApplicationSingleton.getInstance().getRegistry().toString();
    
            Constructor<ApplicationSingleton> c = 
                ApplicationSingleton.class.getDeclaredConstructor();
            c.setAccessible(true);
            ApplicationSingleton applSingleton1 = c.newInstance();
            String rAddress2 = applSingleton1.getRegistry().toString();
    
            ApplicationSingleton applSingleton2 = c.newInstance();
            String rAddress3 = applSingleton2.getRegistry().toString();             
    
    
            // serialization
    
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(byteOut);
            out.writeObject(applSingleton1);
    
            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
            ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();
    
            String rAddress4 = applSingleton3.getRegistry().toString();
    
            List<Registry.Data> data = ApplicationSingleton.getInstance().getData();
            List<Registry.Data> data1 = applSingleton1.getData();
            List<Registry.Data> data2 = applSingleton2.getData();
            List<Registry.Data> data3 = applSingleton3.getData();
    
            System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
            System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
            System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
            System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
                    ApplicationSingleton.getInstance().getInitializedTime(),
                    applSingleton1.getInitializedTime(), 
                    applSingleton2.getInitializedTime(),
                    applSingleton3.getInitializedTime());
        }
    
    }
    

    这是输出:
    Singleton instance is created: 1304068111203
    Singleton instance is created: 1304068111218
    Singleton instance is created: 1304068111234
    applSingleton1=ApplicationSingleton@16cd7d5, applSingleton2=ApplicationSingleton@15b9e68, applSingleton3=ApplicationSingleton@1fcf0ce
    rAddr1=Registry@f72617, rAddr2=Registry@4f1d0d, rAddr3=Registry@1fc4bec, rAddr4=Registry@1174b07
    dAddr1=[Registry$Data@1256ea2, Registry$Data@82701e], dAddr2=[Registry$Data@1f934ad, Registry$Data@fd54d6], dAddr3=[Registry$Data@18ee9d6, Registry$Data@19a0c7c], dAddr4=[Registry$Data@a9ae05, Registry$Data@1dff3a2]
    time0=1304068111203, time1=1304068111218, time2=1304068111234, time3=1304068111218
    

    应该提到的是:
  • 单例实例被创建了好几个!次
  • 所有注册表对象都是具有自己数据的不同对象

  • 所以,总结一下:标准方法对于反射攻击很弱,并且在序列化后返回不同的实例,但是对于相同的数据是可以的。

    因此,似乎 Enum 方法更加可靠和健壮。它是当今在 Java 中使用单例模式的推荐方式吗?你怎么认为?
    解释一个有趣的事实:为什么 enum 中的对象可以与其所属的类一起序列化,但没有实现 Serializable?它是功能还是错误?

    最佳答案

    我不知道现在枚举是构建单例的 Java 方式。但是如果你打算这样做,你也可以直接使用枚举。我看不出有什么好的理由将单例封装在一堆静态成员方法后面;一旦你这样做了,你也可以开始编写一个带有私有(private)静态成员的静态类。

    关于java - 现在更好的 Java 单例模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5822827/

    有关java - 现在更好的 Java 单例模式?的更多相关文章

    1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    3. ruby - 如何在续集中重新加载表模式? - 2

      鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

    4. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    5. ruby-on-rails - 更好的替代方法 try( :output). try( :data). try( :name)? - 2

      “输出”是一个序列化的OpenStruct。定义标题try(:output).try(:data).try(:title)结束什么会更好?:) 最佳答案 或者只是这样:deftitleoutput.data.titlerescuenilend 关于ruby-on-rails-更好的替代方法try(:output).try(:data).try(:name)?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

    6. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    7. ruby-on-rails - 我更新了 ruby​​ gems,现在到处都收到解析树错误和弃用警告! - 2

      简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und

    8. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

      给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

    9. ruby-on-rails - 使用 ruby​​ 将多个实例变量转换为散列的更好方法? - 2

      我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。

    10. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    随机推荐