• 设计模式-----单例模式


    单例模式

    定义

    保证一个类仅有一个实例,并提供一个访问它的全局访问点

    六种写法

    1.饿汉式

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

    **饿汉式是典型的空间换时间,在类装载时进行了对象实例化,不管是否使用都先创建出来,类装载较慢,但提取对象的速度快,饿汉式基于JVM类装载的机制避免了多线程同步问题,但是没有达到懒加载的效果, 如果从始至终从未使用过这个实例,则会造成内存的浪费 **

    2.懒汉式(线程不安全)

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

    懒汉式实例化时机是在第一次调用时,实现Lazy Loading,第一次调用反应稍慢,而且多线程时不安全

    懒汉式(线程安全)

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

    这种写法实现了线程安全,但是每次提取实例对象的时候,都需要进行同步,造成不必要的开销,而且大部分时候我们用不到同步,所以不建议使用这种写法

    3.双重检测锁(DCL)

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

    双重检测锁(Double Checked Locking),有两次对instance的判空,一次在同步块外,次在同步块内,因为有可能多个线程一起进入同步块外的if,如果在同步块内不进行二次检验的话就会生成多个实例了,两次判空也减少了不必要的同步

    注意: 这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情

    1. 给 instance 分配内存
    2. 调用 Singleton 的构造函数来初始化成员变量
    3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

    但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错

    我们只需要将 instance 变量声明成 volatile 就可以了

    4.静态内部类(static nested class)

    public class Singleton { 
    	private static class SingletonHolder {  
    		private static final Singleton INSTANCE = new Singleton();  
    	}  
        private Singleton(){
    	}
    	public static Singleton getInstance(){  
    		return SingletonHolder.INSTANCE;  
    	}  
    } 
    

    **这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,而第一次加载Singleton类时并不会初始化INSTANCE,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化INSTANCE,因此它是懒汉式的,这样不仅能确保线程安全也能保证Singleton类的唯一性, 同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。 所以推荐使用静态内部类单例模式 **

    5.枚举(Enum)

    public enum Singleton{
    	INSTANCE;
    }
    

    可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,不熟悉还有可读性并不是很高

    6.容器

    public class SingletonManager {
        private static Map<String,Object> map=new HashMap<String, Object>();
        private SingletonManager(){}
        
        public static void registerService(String key,Object instance){
            if (!map.containsKey(key)){
                map.put(key,instance);
            }
        }
        
        public static Object getService(String key){
            return map.get(key);
        } 
    }
    

    用SingletonManager 将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度

    总结

    一般来说,单例模式有六种写法:饿汉式 、 懒汉式 、 双重检测锁 、 静态内部 、 枚举 、 容器。

    **在开发中,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy loading)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例 **

  • 相关阅读:
    python appium环境搭建
    github 删除某个文件
    python 导入的模块使用了相对路径,导致找不到文件错误
    python asyncio协程
    python 获取调用函数的名字和行号
    monkey测试命令
    python 属性查询顺序,数据描述符
    JS各循环的差别
    AngularJS复习小结
    那些不正经的前端笔试题
  • 原文地址:https://www.cnblogs.com/MessiXiaoMo3334/p/11845399.html
Copyright © 2020-2023  润新知