zoukankan      html  css  js  c++  java
  • 设计模式之单例模式

    单例模式

    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 {
            Constructor<InnerClassSingleton> declaredConstructor = 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 {
            Constructor<InnerClassSingleton> declaredConstructor = 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);*/
            Constructor<EnumSingleton> declaredConstructor = 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.
    *
    * <PRE>
    * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
    * </PRE><p>

    自定义实现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 class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> 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;
    }
    }
  • 相关阅读:
    某不知名的树形Dp
    HDU-5963 朋友 思维
    CF1292C Xenon's Attack on the Gangs
    Emergency Evacuation 模拟了一下
    NOI2003 逃学的小孩
    UVA11300 Spreading the Wealth 数学
    ACWing 1510 楼梯
    测试代码高亮
    Pollard-rho的质因数分解
    米勒罗宾素数检测(Miller-Rabin)
  • 原文地址:https://www.cnblogs.com/kangzhipeng/p/12153499.html
Copyright © 2011-2022 走看看