在Java中实现单例设计模式的有效方法是什么?
当前回答
我可以说是枚举单例。
在Java中使用枚举通常是声明枚举单例的一种方法。枚举单例可以包含实例变量和实例方法。为了简单起见,还要注意,如果您正在使用任何实例方法,那么如果该方法影响对象的状态,则需要确保该方法的线程安全。
枚举的使用非常容易实现,并且在可序列化对象方面没有缺点,必须通过其他方式规避这些缺点。
/**
* Singleton pattern example using a Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
// Perform operation here
}
}
您可以通过Singleton.INSTANCE访问它,它比在Singleton上调用getInstance()方法简单得多。
1.12枚举常量的序列化枚举常量的序列化方式不同于普通的可序列化或可外部化对象。枚举常量的序列化形式仅由其名称组成;表单中不存在常量的字段值。若要序列化枚举常量,ObjectOutputStream将写入枚举常量的name方法返回的值。要反序列化枚举常量,ObjectInputStream从流中读取常量名称;然后通过调用java.lang.Enum.valueOf方法获得反序列化的常量,并将常量的枚举类型与接收到的常量名称作为参数一起传递。与其他可序列化或可外部化对象一样,枚举常量可以用作后续在序列化流中出现的反向引用的目标。无法自定义枚举常量的序列化过程:在序列化和反序列化期间,将忽略枚举类型定义的任何类特定的writeObject、readObject、readObjectNoData、writeReplace和readResolve方法。类似地,任何serialPersistentFields或serialVersionUID字段声明也会被忽略——所有枚举类型都具有固定的serialVersionID 0L。记录枚举类型的可序列化字段和数据是不必要的,因为发送的数据类型没有变化。引自Oracle文档
传统singleton的另一个问题是,一旦实现了Serializable接口,它们就不再是单一的,因为readObject()方法总是返回一个新的实例,就像Java中的构造函数一样。这可以通过使用readResolve()并丢弃新创建的实例来避免,方法是将其替换为单例,如下所示:
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
如果您的单例类保持状态,这可能会变得更加复杂,因为您需要使它们成为瞬态的,但是在枚举单例中,JVM保证了序列化。
好的阅读
Singleton模式枚举、Singleton和反序列化双重检查锁定和Singleton模式
其他回答
枚举单例
实现线程安全的单例的最简单方法是使用Enum:
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
自从在Java 1.5中引入Enum以来,此代码一直有效
双重检查锁定
如果你想编写一个在多线程环境中工作的“经典”单例(从Java1.5开始),你应该使用这个。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这在1.5之前不是线程安全的,因为volatile关键字的实现不同。
早期加载单例(甚至在Java1.5之前就可以使用)
这个实现在加载类时实例化单例,并提供线程安全性。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
以下是三种不同的方法
枚举/***使用Java Enum的Singleton模式示例*/公共枚举EasySingleton{实例;}双重检查锁定/延迟加载/***带有双重检查锁定的单线模式示例*/公共类DoubleCheckedLockingSingleton{私有静态易失性DoubleCheckedLockingSingleton INSTANCE;private DoubleCheckedLockingSingleton(){}公共静态DoubleCheckedLockingSingleton getInstance(){如果(INSTANCE==null){synchronized(DoubleCheckedLockingSingleton.class){//双重检查Singleton实例如果(INSTANCE==null){INSTANCE=新的DoubleCheckedLockingSingleton();}}}返回INSTANCE;}}静态工厂方法/***带有静态工厂方法的Singleton模式示例*/公开课Singleton{//在类加载期间初始化private static final Singleton INSTANCE=new Singleton();//防止创建另一个“Singleton”实例私有Singleton(){}公共静态Singleton getSingleton(){返回INSTANCE;}}
有时简单的“static Foo=new Foo();”是不够的。想想你想做的一些基本数据插入。
另一方面,您必须同步任何实例化单例变量的方法。同步本身并不坏,但它可能会导致性能问题或锁定(在非常罕见的情况下,使用本示例。解决方案是
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
现在会发生什么?类通过类加载器加载。在从字节数组解释类之后,VM立即执行静态{}-块。这就是全部秘密:静态块只被调用一次,即这个单类加载器加载给定包的给定类(名称)的时间。
如果您不需要延迟加载,那么只需尝试:
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
如果您希望延迟加载并且希望单例是线程安全的,请尝试双重检查模式:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
由于双重检查模式不能保证有效(由于编译器的一些问题,我对此一无所知),因此您也可以尝试同步整个getInstance方法或为所有单例创建注册表。
我见过的最好的单例模式使用Supplier接口。
它是通用的,可重复使用它支持延迟初始化它只有在初始化之前才同步,然后将阻塞供应商替换为非阻塞供应商。
见下文:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap