单例模式
1.懒汉模式:延迟加载,只有在真正使用的时候,才开始初始化。
1)线程安全问题
2)double check 加锁优化
3)编译器(JIT)、CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰
对于volatile修饰的字段,可以防止指令重排。
public class LazySingletonTest { public static void main(String[] args) { LazySingleton instance = LazySingleton.getInstance(); LazySingleton instance1 = LazySingleton.getInstance(); System.out.println(instance == instance1); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance() { if(instance == null){ instance = new LazySingleton(); } return instance; } }
在多线程环境下会出现问题:如下面代码:
public class LazySingletonTest { public static void main(String[] args) { new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance() { if(instance == null){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; } }
打印结果:
com.kzp.designpattern.lazysingleton.LazySingleton@206683de
com.kzp.designpattern.lazysingleton.LazySingleton@37d3a78
在多线程环境下,打印了两个不同的对象,getInstance方法前加入synchronized关键字可解决该多线程问题。
public class LazySingletonTest { public static void main(String[] args) { /* LazySingleton instance = LazySingleton.getInstance(); LazySingleton instance1 = LazySingleton.getInstance(); System.out.println(instance == instance1);*/ new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){} public synchronized static LazySingleton getInstance() { if(instance == null){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; } }
但加入synchronized会有性能问题,下面是用双重检查机制来创建单例对象。
public class LazySingletonTest { public static void main(String[] args) { /* LazySingleton instance = LazySingleton.getInstance(); LazySingleton instance1 = LazySingleton.getInstance(); System.out.println(instance == instance1);*/ new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private volatile static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance() { if(instance == null){ synchronized (LazySingleton.class){ if(instance == null) { instance = new LazySingleton(); } } } return instance; } }
初始化对象的步骤为:
//1.分配空间
//2.初始化
//3.引用赋值
加入volatile可以防止指令的重排序
2.饿汉模式:
类加载的初始化阶段就完成了实例的初始化,本质上就是借助于JVM类加载机制,保证实例的唯一性。
类加载过程:
1.加载二进制数据到内存中,生成对应的Class数据结构。
2.连接:a:验证 b:准备(给类的静态成员变量赋默认值)c:解析
3.初始化:给类的静态变量赋初值。
只有在真正使用对应的类时,才会触发初始化 如(当前类是启动类即main函数所在类,直接进行new 操作,访问静态属性,访问静态方法,
用反射访问类,初始化一个类的子类等)
public class HungrySingletonTest { public static void main(String[] args) { HungrySingleton instance = HungrySingleton.getInstance(); HungrySingleton instance1 = HungrySingleton.getInstance(); System.out.println(instance == instance1); } } class HungrySingleton{ private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance() { return instance; } }
3.静态内部类
1)本质上是利用类的加载机制来保证线程安全。
2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
public class InnerClassSingletonTest { public static void main(String[] args) { InnerClassSingleton instance = InnerClassSingleton.getInstance(); InnerClassSingleton instance1 = InnerClassSingleton.getInstance(); System.out.println(instance == instance1); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
多线程下代码:
public class InnerClassSingletonTest { public static void main(String[] args) { new Thread(()->{ InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(instance); }).start(); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
打印结果为
com.kzp.designpattern.innerclasssingleton.InnerClassSingleton@206683de
com.kzp.designpattern.innerclasssingleton.InnerClassSingleton@206683de
因此这种方式是线程安全的。
但是,可以通过发射来创建多实例,违反单例模式:
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { ConstructordeclaredConstructor = InnerClassSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance(); InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton == instance); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
但静态内部类和饿汉模式可以防止创建多实例,懒汉模式则不可以
通过在私有构造函数中判断对象是否存在,存在则抛出异常来避免反射创建多实例。
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { ConstructordeclaredConstructor = InnerClassSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance(); InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton == instance); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ if(InnerClassHolder.instance != null){ throw new RuntimeException("单例不允许多个实例"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
4.枚举单实例方式
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) { EnumSingleton instance = EnumSingleton.INSTANCE; EnumSingleton instance1 = EnumSingleton.INSTANCE; System.out.println(instance == instance1); } }
下面通过反射来创建枚举单实例对象
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { /* EnumSingleton instance = EnumSingleton.INSTANCE; EnumSingleton instance1 = EnumSingleton.INSTANCE; System.out.println(instance == instance1);*/ ConstructordeclaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class); declaredConstructor.setAccessible(true); declaredConstructor.newInstance("INSTANCE",0); } }
抛出异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:402)
at com.kzp.designpattern.enumsingleton.EnumTest.main(EnumSingleton.java:19)
序列化与反序列化实例对象后,单例对象遭到了破坏。
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { //测试序列化 InnerClassSingleton instance = InnerClassSingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable")); oos.writeObject(instance); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable")); InnerClassSingleton object = (InnerClassSingleton) ois.readObject(); System.out.println(instance == object); } } class InnerClassSingleton implements Serializable { private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ if(InnerClassHolder.instance != null){ throw new RuntimeException("单例不允许多个实例"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
结果返回false
在Serializable接口中有这样的描述:
Classes that need to designate a replacement when an instance of it * is read from the stream should implement this special method with the * exact signature. * ** ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; *
自定义实现readResolve可解决这个问题
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { //测试序列化 InnerClassSingleton instance = InnerClassSingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable")); oos.writeObject(instance); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable")); InnerClassSingleton object = (InnerClassSingleton) ois.readObject(); System.out.println(instance == object); } } class InnerClassSingleton implements Serializable { private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ if(InnerClassHolder.instance != null){ throw new RuntimeException("单例不允许多个实例"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } Object readResolve() throws ObjectStreamException{ return InnerClassHolder.instance; }; }
但是加入这个之后,会报
Exception in thread "main" java.io.InvalidClassException: com.kzp.designpattern.innerclasssingleton.InnerClassSingleton; local class incompatible: stream classdesc serialVersionUID = 595850868840365671, local class serialVersionUID = 7367063990306020841 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) at com.kzp.designpattern.innerclasssingleton.InnerClassSingletonTest.main(InnerClassSingletonTest.java:32)
这是因为我们没有给需要序列化的类给一个serialVersionUID,导致序列化和反序列化时认为为不同的类。
InnerClassSingleton类中加入serialVersionUID即可解决该问题。
枚举类型的单实例则不存在这样的反序列化问题。
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { EnumSingleton instance = EnumSingleton.INSTANCE; // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumsingleton")); // oos.writeObject(instance); // oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumsingleton")); EnumSingleton object = ((EnumSingleton) ois.readObject()); System.out.println(instance == object); } }
结果为true。
源码中的应用
//Spring & JDK java.lang.Runtime org.springframework.aop.framework.ProxyFactoryBean org.springframework.beans.factory.support.DefaultSingletonBeanRegistry org.springframework.core.ReactiveAdapterRegistry //Tomcat org.apache.catalina.webresources.TomcatURLStreamHandlerFactory //反序列化指定数据源 java.util.Currency
单例模式在JDK中的应用:
Runtime类
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of classRuntime
are instance * methods and must be invoked with respect to the current runtime object. * * @return theRuntime
object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {}
使用到的是饿汉模式。
Currency类中用到了解决反序列化问题的方法。
public final class Currency implements Serializable private final String currencyCode; private Object readResolve() { return getInstance(currencyCode); } }
Spring中DefaultSingletonBeanRegistry类使用到了单例模式
另外,ReactiveAdapterRegistry(Srping5版本才有的类)使用到了双重检查模式:
public class ReactiveAdapterRegistry { @Nullable private static volatile ReactiveAdapterRegistry sharedInstance; public static ReactiveAdapterRegistry getSharedInstance() { ReactiveAdapterRegistry registry = sharedInstance; if (registry == null) { Class var1 = ReactiveAdapterRegistry.class; synchronized(ReactiveAdapterRegistry.class) { registry = sharedInstance; if (registry == null) { registry = new ReactiveAdapterRegistry(); sharedInstance = registry; } } } return registry; } }