首页
设计模式
Linux
云原生
常用bat文件
Maven教程
mongodb
Oracle常用知识梳理
更多……
申请加入课程
单例模式
外观模式
设计原则
工厂模式
建造者模式
原型模式
适配器模式
桥接模式
过滤器模式
组合模式
装饰器模式
享元模式
代理模式
责任链模式
命令模式
解释器模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
空对象模式
策略模式
模板模式
访问者模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式
前端控制器模式
拦截过滤器模式
服务定位器模式
传输对象模式
单例模式
星辰
2018-05-29
0
0
3421
人
0
人评论
0
人举报
# 单例模式 > * 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 > * 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 **注意:** * 1、单例类只能有一个实例。 * 2、单例类必须自己创建自己的唯一实例。 * 3、单例类必须给所有其他对象提供这一实例。 # 介绍 * 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 * 主要解决:一个全局使用的类频繁地创建与销毁。 * 何时使用:当您想控制实例数目,节省系统资源的时候。 * 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。 * 关键代码:构造函数是私有的。 * 应用实例: 1、一个党只能有一个主席。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。 * 优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。 * 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。 * 使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。 * 注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。 # 恶汉模式实现 > 提前new出来实例了,并不是在第一次调用get方法时才实例化,没有进行延迟加载。 ``` package com.noteshare.designPatterns.singleton; /** * @Title: 饿汉模式:提前new出来实例了,并不是在第一次调用get方法时才实例化,没有进行延迟加载。 * 此处为了更好了的理解你需要去了解下类的饿汉和懒汉加载方式 * @author NoteShare * @since JDK1.8 * @history 2017年10月24日 */ public class EagerlySingleton { //类加载时进行对象实例化 private static EagerlySingleton eagerlySingleton = new EagerlySingleton(); /** * 私有的构造方法,不让在外部进行类的实例化 */ private EagerlySingleton(){ } /** * @Title : getInstance * @Description : 开放对外的获取实例的方法 * @author : NoteShare * Create Date : 2017年10月24日 下午4:09:19 * @return * @throws */ public static EagerlySingleton getInstance(){ return eagerlySingleton; } } ``` # 懒汉模式 > 需要用到时再加载,此处有线程安全的实现和线程不安全的实现,具体看代码 ``` package com.noteshare.designPatterns.singleton; /** * @Title: 懒汉模式 * @author NoteShare * @since JDK1.8 * @history 2017年10月24日 */ public class LazySingleton { private static LazySingleton lazySingleton; /** * 定义私有的构造方法,防止外部进行创建对象 */ private LazySingleton(){ } /** * @Title : getInstance * @Description : 定义向外公布对象的接口 * 缺点:多线程环境下无法保证单例效果,会多次执行 instance=new Singleton(),需要考虑到多线程 * @author : NoteShare * Create Date : 2017年10月25日 下午7:46:19 * @return * @throws */ public static LazySingleton getInstance(){ if(null == lazySingleton){ lazySingleton = new LazySingleton(); } return lazySingleton; } /** * @Title : getSyncInstance * @Description : 同步的获取对象的方法 * 同步方法方式:性能不高,同步范围太大,在实例化instacne后,获取实例仍然是同步的,效率太低,需要缩小同步的范围。 * @author : NoteShare * Create Date : 2017年10月25日 下午7:50:52 * @return * @throws */ public static synchronized LazySingleton getSyncInstance(){ if(null == lazySingleton){ lazySingleton = new LazySingleton(); } return lazySingleton; } /** * @Title : getSyncBlockInstance * @Description : 同步的获取对象的方法 * 缺点:缩小同步范围,来提高性能,但是仍然存在多次执行instance=new Singletom()的可能,由此引出double check * @author : NoteShare * Create Date : 2017年10月25日 下午7:53:45 * @return * @throws */ public static LazySingleton getSyncBlockInstance(){ if(null == lazySingleton){ synchronized (LazySingleton.class) { lazySingleton = new LazySingleton(); } } return lazySingleton; } /** * @Title : getInstanceByDoubleCheck * @Description : 缺点:避免的上面方式的明显缺点,但是java内存模型(jmm)并不限制处理器重排序,在执行instance=new Singleton();时,并不是原子语句,实际是包括了下面三大步骤: * 1.为对象分配内存 2.初始化实例对象 3.把引用instance指向分配的内存空间 这个三个步骤并不能保证按序执行,处理器会进行指令重排序优化,存在这样的情况: 优化重排后执行顺序为:1,3,2, 这样在线程1执行到3时,instance已经不为null了,线程2此时判断instance!=null,则直接返回instance引用,但现在实例对象还没有初始化完毕,此时线程2使用instance可能会造成程序崩溃。 现在要解决的问题就是怎样限制处理器进行指令优化重排。 解决以上问题请查看VolatileSingleton实现。 * @author : NoteShare * Create Date : 2017年10月25日 下午7:58:47 * @return * @throws */ public static LazySingleton getInstanceByDoubleCheck(){ if(null == lazySingleton){ synchronized (LazySingleton.class) { if(null == lazySingleton){ lazySingleton = new LazySingleton(); } } } return lazySingleton; } } ``` # 静态内部类懒汉模式 > 静态内部类在没有显示调用的时候是不会进行加载的,当执行了return InstanceHolder.instance后才加载初始化,这样就实现了正确的单例模式 ``` package com.noteshare.designPatterns.singleton; /** * @ClassName : StaticSingleton * @Description : 静态内部类懒汉模式 * 静态内部类在没有显示调用的时候是不会进行加载的,当执行了return InstanceHolder.instance后才加载初始化,这样就实现了正确的单例模式。 * @author : NoteShare * @date : 2017年10月30日 下午11:06:12 */ public class StaticSingleton { private StaticSingleton(){ } public static StaticSingleton getInstance(){ return InstanceHandler.staticSingleton; } static class InstanceHandler{ private static StaticSingleton staticSingleton = new StaticSingleton(); } } ``` # 使用volatile关键字修饰instance就可以实现正确的double check单例模式了 ``` package com.noteshare.designPatterns.singleton; /** * @Title: 在JDK1.5之后,使用volatile关键字修饰instance就可以实现正确的double check单例模式了 * @author NoteShare * @since JDK1.8 * @history 2017年10月27日 */ public class VolatileDoubleCheckSingleton { private static volatile VolatileDoubleCheckSingleton volatileDoubleCheckSingleton; private VolatileDoubleCheckSingleton(){ } /** * @Title : getInstance * @Description : 这里就要介绍一下volatile的作用了: 1.保证可见性 可以保证在多线程环境下,变量的修改可见性。每个线程都会在工作内存(类似于寄存器和高速缓存),实例对象都存放在主内存中, 在每个线程要使用的时候把主内存中的内容拷贝到线程的工作内存中。使用volatile关键字修饰后的变量,保证每次修改了变量需要立即写回主内存中, 同时通知所有的该对变量的缓存失效,保证缓存一致性,其他线程需要使用该共享变量时就要重新从住内存中获取最新的内容拷贝到工作内存中供处理器使用。 这样就可以保证变量修改的可见性了。但volatile不能保证原子性,比如++操作。 2.提供内存屏障 volatile关键字能够通过提供内存屏障,来保证某些指令顺序处理器不能够优化重排,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。 下面是保守策略插入内存屏障: 在每个volatile写操作的前面插入一个StoreStore屏障。 在每个volatile写操作的后面插入一个StoreLoad屏障。 在每个volatile读操作的前面插入一个LoadLoad屏障。 在每个volatile读操作的后面插入一个LoadLoad屏障。 这样可以保证在volatile关键字修饰的变量的赋值和读取操作前后两边的大的顺序不会改变,在内存屏障前面的顺序可以交换,屏障后面的也可以换序,但是不能跨越内存屏障重排执行顺序。 好了,现在来看上面的单例模式,这样就可以保证3步骤(instance赋值操作)是保持最后一步完成,这样就不会出现instance在对象没有初始化时就不为null的情况了。这样也就实现了正确的单例模式了。 * @author : NoteShare * Create Date : 2017年10月27日 下午5:23:13 * @return * @throws */ public static VolatileDoubleCheckSingleton getInstance(){ if(null != volatileDoubleCheckSingleton){ synchronized (VolatileDoubleCheckSingleton.class) { if(null != volatileDoubleCheckSingleton){ volatileDoubleCheckSingleton = new VolatileDoubleCheckSingleton(); } } } return volatileDoubleCheckSingleton; } } ```
所有评论列表
点我发表评论