面试官:带笔了吧,那写两种单例模式的实现方法吧沙沙沙刷刷刷~~~ 写好了java
面试官:你这个是怎么保证线程安全的,那你知道,volatile 关键字? 类加载器?锁机制????git
点赞+收藏 就学会系列,文章收录在 GitHub JavaEgg ,N线互联网开发必备技能兵器谱程序员
单例模式,从我看 《Java 10分钟入门》那天就听过的一个设计模式,还被面试过好几回的设计模式问题,今天一网打尽~~github
有一些对象咱们确实只须要一个,好比,线程池、数据库链接、缓存、日志对象等,若是有多个的话,会形成程序的行为异常,资源使用过量或者不一致的问题。你也许会说,这种我用全局变量不也能实现吗,还整个单例模式,好像你很流弊的样子,若是将对象赋值给一个全局变量,那程序启动就会建立好对象,万一这个对象很耗资源,咱们还可能在某些时候用不到,这就形成了资源的浪费,不合理,因此就有了单例模式。面试
单例模式确保一个类只有一个实例,并提供一个全局惟一访问点数据库
用这两个知识点写出的单例类就是饿汉式了,初始化类的时候就建立,饥不择食,饿汉设计模式
public class Singleton { //构造私有化,防止直接new private Singleton(){} //静态初始化器(static initializer)中建立实例,保证线程安全 private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
饿汉式是线程安全的,JVM在加载类时立刻建立惟一的实例对象,且只会装载一次。缓存
Java 实现的单例是一个虚拟机的范围,由于装载类的功能是虚拟机的,因此一个虚拟机经过本身的ClassLoader 装载饿汉式实现单例类的时候就会建立一个类实例。(若是一个虚拟机里有多个ClassLoader的话,就会有多个实例)安全
懒汉式,就是实例在用到的时候才去建立,比较“懒”session
单例模式的懒汉式实现方式体现了延迟加载的思想(延迟加载也称懒加载Lazy Load,就是一开始不要加载资源或数据,等到要使用的时候才加载)
public class Singleton { private static Singleton singleton; private Singleton(){} //解决了线程不安全问题,可是效率过低了,每一个线程想得到类的实例的时候,都须要同步方法,不推荐 public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
public class Singleton { //volatitle关键词确保,多线程正确处理singleton private static volatile Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton ==null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
Double-Check 概念(进行两次检查)是多线程开发中常用的,为何须要双重检查锁呢?由于第一次检查是确保以前是一个空对象,而非空对象就不须要同步了,空对象的线程而后进入同步代码块,若是不加第二次空对象检查,两个线程同时获取同步代码块,一个线程进入同步代码块,另外一个线程就会等待,而这两个线程就会建立两个实例化对象,因此须要在线程进入同步代码块后再次进行空对象检查,才能确保只建立一个实例化对象。
双重检查加锁(double checked locking)线程安全、延迟加载、效率比较高
volatile:volatile通常用于多线程的可见性,这里用来防止指令重排(防止new Singleton时指令重排序致使其余线程获取到未初始化完的对象)。被volatile 修饰的变量的值,将不会被本地线程缓存,全部对该变量的读写都是直接操做共享内存,从而确保多个线程能正确的处理该变量。
指令重排是指在程序执行过程当中, 为了性能考虑, 编译器和CPU可能会对指令从新排序。
Java中建立一个对象,每每包含三个过程。对于singleton = new Singleton(),这不是一个原子操做,在 JVM 中包含以下三个过程。
可是,因为JVM会进行指令重排序,因此上面的第二步和第三步的顺序是不能保证的,最终的执行顺序多是 1-2-3,也多是 1-3-2。若是是 1-3-2,则在 3 执行完毕,2 未执行以前,被另外一个线程抢占了,这时 instance 已是非 null 了(但却没有初始化),因此这个线程会直接返回 instance,而后使用,那确定就会报错了,因此要加入 volatile关键字。
public class Singleton { private Singleton(){} private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.INSTANCE; } }
采用类加载的机制来保证初始化实例时只有一个线程;
静态内部类方式在Singleton 类被装载的时候并不会当即实例化,而是在调用getInstance的时候,才去装载内部类SingletonInstance ,从而完成Singleton的实例化
类的静态属性只会在第一次加载类的时候初始化,因此,JVM帮咱们保证了线程的安全性,在类初始化时,其余线程没法进入
优势:线程安全,利用静态内部类实现延迟加载,效率较高,推荐使用
enum Singleton{ INSTANCE; public void method(){} }
借助JDK5 添加的枚举实现单例,不只能够避免多线程同步问题,还能防止反序列化从新建立新的对象,可是在枚举中的其余任何方法的线程安全由程序员本身负责。还有防止上面的经过反射机制调用私用构造器。不过,因为Java1.5中才加入enum特性,因此使用的人并很少。
这种方式是《Effective Java》 做者Josh Bloch 提倡的方式。
JDK 中,java.lang.Runtime
就是经典的单例模式(饿汉式)