zoukankan      html  css  js  c++  java
  • 【设计模式】并发编程下的单例模式

    一、饿汉单例

    1. 静态变量实现

    public class Singleton {
        private static Singleton instance = new Singleton();
        private Singleton() {}
        
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    这种实现方式基于class loader机制避免了多线程的同步问题。不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

    2. 静态代码块实现

    public class Singleton {
        private Singleton instance = null;
        private Singleton() {}
        static {
            instance = new Singleton();
        }
        
        public static Singleton getInstance() {
            return this.instance;
        }
    }
    

    这种实现方式表面上看起来与上面差别挺大的,其实跟上面的差别不大,都是在类初始化即实例化instance。

    二、懒汉单例

    1. 基本实现,线程不安全

    public class Singleton {
        
        private static Singleton instance;
        private Singleton() {}
        
        public static Singleton getInstance() {
            if (instance == null) {
    			instance = 	new Singleton();
            }
            return instance;
        }
    }
    

    这种是最基本的lazy loading实现的单例模式,但是缺点很明显,在多线程下显然不能安全工作。

    2. synchronized同步静态方法,线程安全

    public class Singleton {
        
        private static Singleton instance;
    	private Singleton() {}
        
        public static synchronized Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    这种写法能够很好的在多线程中工作,不会出现并发安全问题,也实现了懒加载。但是效率很低,synchronized使得这里并行变成串行。所以这种写法一般不会被使用到。

    3. 静态内部类实现,推荐使用

    public class Singleton {
        
        // === 静态内部类 ===
        private static class SingletonHolder {
            private static final INSTANCE = new Singleton();
        }
        
        private Singleton() {}
        
        public static final Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    这种方式同样利用了class loader 的机制来保证初始化instance时只有一个线程,它跟上面提到的饿汉单例不同的是(很细微的差别):上面的饿汉单例是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy-loading效果)。而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示调用getInstance()方法时,才会显示装载SingletonHolder类,从而实例化instance。

    4. 双重校验锁实现,推荐使用

    public class Singleton {
        // ===1:volatile修饰
        private volatile static Singleton singleton;
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            // ===2:减少不必要同步,性能优化
            if (singleton == null) {
                // ===3:同步,线程安全
                synchronized(Singleton.class) {
                    if (singleton == null) {
                        // ===4:创建singleton对象
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    
    • 为什么要使用volatile修饰?

      虽然已经使用synchronized进行同步,但是第四步创建对象时,会有下面的伪代码:

      memory=allocate();  // 1. 分配内存空间
      ctorInstance();     // 2. 初始化对象
      singleton=memory;   // 3. 设置singleton指向刚排序的内存空间
      

      当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以 JVM 是允许的。如果此时伪代码发生重排序,步骤为1 -> 3 -> 2,线程A执行到第3步时,线程B调用getInstance()方法,在判断singleton == null时不为null,则返回singleton。但此时singleton并没有初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!

    5. 枚举实现,推荐使用

    public class Singleton {
        
        private Singleton(){}
        
        // === 延迟加载
        private enum EnumHolder {
            INSTANCE;
            private static Singleton instance = null;
            
            private Singleton getInstance() {
                instance = new Singleton();
            	return instance;
            }
        }
        
        public static Singleton getInstance() {
            return EnumHolder.INSTANCE.instance;
        }
    }
    

    三、问题注意

    1. 不同类加载器加载

    如果单例由不同的类加载器加载,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些Servlet容器对每个Servlet使用完全不同的类加载器,这样的话如果有两个Servlet访问一个单例类,它们就都会有各自的实例。

    可以用下面方式对这个问题进行修复:

    private static Class getClass(String classname) throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if(classLoader == null) {
             classLoader = Singleton.class.getClassLoader();        
        }      
        return (classLoader.loadClass(classname));   
    }
    

    2. 序列化问题

    如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原,不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

    public class Singleton implements java.io.Serializable {     
        public static Singleton INSTANCE = new Singleton();     
    
        protected Singleton() {}
        
        private Object readResolve() {     
            return INSTANCE;     
        }
    }  
    
  • 相关阅读:
    改造我们的学习:有钱不会花,抱着金库抓瞎
    (转)我奋斗了18年才和你坐在一起喝咖啡
    初学者要知道的十件事
    [转]C#图像处理 (各种旋转、改变大小、柔化、锐化、雾化、底片、浮雕、黑白、滤镜效果)
    C#调用系统的复制、移动、删除文件对话框
    SQLite数据类型
    jquery禁用dropdownlist中某一项
    C# winform无标题窗体随意移动
    注册.NET Framework
    jQuery同步/异步调用后台方法
  • 原文地址:https://www.cnblogs.com/jojop/p/14111507.html
Copyright © 2011-2022 走看看