单例模式,简而言之就是在整个应用程序里面有且仅有一个实例,在程序的任什么时候候,任何地方获取到的该对象都是同一个对象。单例模式解决了一个全局的类被频繁建立和销毁的,或者每次建立或销毁都须要消耗大量cpu资源的对象的问题。单例模式总的能够分为懒汉模式和饿汉模式,顾名思义,懒汉模式是一个很是懒的汉子,只要你没有使用到它,它就永远不会实例化。饿汉模式的意思就是,汉子很是饥渴,只要在程序的编译阶段就给你分配内存,建立好对象。java
将懒汉模式和饿汉模式细分,又能够分为:程序员
一、懒汉模式安全
二、饿汉模式多线程
三、双检模式jvm
四、静态内部类模式线程
五、枚举模式设计
不论是用哪种方式实现的单例模式,其建立流程基本都是一直的:首先将构造方法声明为private的,这样就防止直接new出一个新的对象。第二,声明一个私有的成员变量,即单例对象。第三步,声明一个public的静态方法,用于获取或建立单例对象,外部想要获取该对象必须经过这个方法获取。指针
1、懒汉模式1--线程安全对象
/** * 饿汉模式1 */ public class HungrySingleton1 { private static HungrySingleton1 singleton = new HungrySingleton1(); private HungrySingleton1(){} public static HungrySingleton1 getInstance() { return singleton; } }
这种懒汉模式的优势是实现很是简单。缺点是并起到懒加载的效果,若是项目没有使用到这个对象的就会形成资源的浪费。blog
2、饿汉模式1--线程不安全
/** * 懒汉模式1 */ public class LazySingleton1 { private static LazySingleton1 singleton; private LazySingleton1(){} public static LazySingleton1 getInstance() { if (singleton == null) { singleton = new LazySingleton1(); } return singleton; } }
这种写法虽然实现了懒加载的效果,可是严格意义上并非单例模式,由于在多线程的环境下有可能会建立出多个不一样的对象,至于为何,不懂的能够看一下我之间写的关于Java内存模型的文章。这种写法只能应用于单线程的环境下,局限性很大。实际中强烈不建议使用这种方法。
3、懒汉模式2--线程安全
/** * 懒汉模式2 */ public class LazySingleton2 { private static LazySingleton2 singleton; private LazySingleton2(){} public static synchronized LazySingleton2 getInstance() { if (singleton == null) { singleton = new LazySingleton2(); } return singleton; } }
这种写法咋看跟上面的方法同样,这种写法在方法上添加了 synchronized 关键字,这样就保证了每次只能有一个线程进入方法体中,解决了懒汉模式1中出现的问题。这种写法的优势是实现了懒加载的效果,缺点是效率很是低,当多个线程同时获取实例时,有可能会形成线程阻塞的状况。不推荐使用。
懒汉模式3--线程不安全
/** * 懒汉模式3 */ public class LazySingleton3 { private static LazySingleton3 singleton; private LazySingleton3(){} public static LazySingleton3 getInstance() { if (singleton == null) { synchronized (LazySingleton3.class) { if (singleton == null) { singleton = new LazySingleton3(); } } } return singleton; } }
这种写法进行了两次 singleton == null 的判断,在实际的应用中当咱们调用这个方法时,其实99%的概率是实例就已经建立好了,所以第一个 singleton == null 能过滤掉99%的调用,不用将方法锁起来,从而提升了效率。这种方法的优势是实现懒加载的效果,效率和很高。缺点是代码设计仍而后缺陷,jvm在为对象分配内存和赋值并非一个原子操做,即 singleton = new LazySingleton3() 这段代码在jvm中是由三个步骤实现的,首先jvm会在堆中为对象分配必定的内存空间,而后完成对象的初始化工做,而后将内存地址指向到对象中。可是,咱们知道,jvm在编译的时候并不老是根据咱们编写的代码的顺序来执行了,而是根据jvm以为最优的顺序执行(这个过程就叫作指令重排序),因此有可能在执行了步骤1后就执行了步骤3,这时候第二个线程进来的发现singleton并不为空,所以就直接返回了该对象,所以形成空指针异常。
4、双重检查锁模式---线程安全
/** * 懒汉模式4 */ public class LazySingleton4 { private volatile static LazySingleton4 singleton; private LazySingleton4(){} public static LazySingleton4 getInstance() { if (singleton == null) { synchronized (LazySingleton4.class) { if (singleton == null) { singleton = new LazySingleton4(); } } } return singleton; } }
相较于上面的方式,这种方式只是在成员变量中添加了 volatile 关键字,解决了指令重排序的问题,同时确保当前线程修改了这个变量时,其余的线程可以及时读到最新的值。这种方法缺点是写起来比较复杂,要求程序员对jvm比较理解。优势是既保证了线程安全,同时也可以保证了比较高的效率。
5、静态内部类模式--线程安全
/** * 懒汉模式5 */ public class LazySingleton5 { private LazySingleton5(){} private static class Holder { private static final LazySingleton5 INSTANCE = new LazySingleton5(); } public static LazySingleton5 getInstance() { return Holder.INSTANCE; } }
这种写法实现比较简单,即实现了懒加载的效果,同时也保证的多线程环境下的线程安全问题。推荐使用这种方式。
6、枚举模式 -- 线程安全
/** * 懒汉模式6 */ public enum LazySingleton6 { INSTANCE }
//使用方法
public class Test {
public static void main(String[] args) {
LazySingleton6 instance = LazySingleton6.INSTANCE;
LazySingleton6 instance1 = LazySingleton6.INSTANCE;
System.out.println(instance == instance1);
}
}
推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是自然支持多线程的。