单例模式可以分为懒汉式和饿汉式:
懒汉式单例模式:在类加载时不初始化,不调用不初始化,一旦调用就只初始化一次。
饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。
饿汉式第一种:基于类加载机制避免了多线程的同步问题(JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的)
缺点:类加载时创建实例,比较消耗系统资源(要是应用没有使用这个实例,系统资源就白白浪费了)
/** * 单例模式:饿汉式,第一种 * 缺点:类加载时创建实例,比较消耗系统资源(要是应用没有使用这个实例,系统资源就白白浪费了) * * @author :liuqi * @date :2018-06-13 15:13. */ public class SingletonDemo1 { private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1() { } public static SingletonDemo1 getInstance() { return instance; } }
为了避免这种情况,我们通常使用惰性加载的机制,也就是在使用的时候才去创建。
懒汉式第一种:不能在多线程下使用
缺点:线程不安全
/** * 单例模式:懒汉式,第一种 * 缺点:线程不安全 * * @author :liuqi * @date :2018-06-13 15:17. */ public class SingletonDemo2 { private static SingletonDemo2 instance; private SingletonDemo2() { } public static SingletonDemo2 getInstance() { if (instance == null) { instance = new SingletonDemo2(); } return instance; } }
为防止多线程情况下创建多个实例的问题(A线程和B线程都进入if判断),使用Class锁机制
懒汉式第二种:在getinstance方法上加synchronized锁
缺点:线程安全但是很影响性能,每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
/** * 单例模式:懒汉式,第二种 * 缺点:线程安全但是很影响性能,每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了 * * @author :liuqi * @date :2018-06-13 15:20. */ public class SingletonDemo3 { private static SingletonDemo3 instance; private SingletonDemo3() { } public static synchronized SingletonDemo3 getInstance() { if(instance == null){ instance = new SingletonDemo3(); } return instance; } }
有人为了解决以上问题,提出了double-checked locking(双重检验锁)的解决方案
懒汉式第三种:双重检验锁
缺点:看似已经解决上述问题,但是对于jvm来说仍然可能会发生错误,在JDK1.5之后,双重检查锁定才能够正常达到单例效果
/** * 单例模式:懒汉式,第三种,双重检验锁 * 缺点:看似已经解决上述问题,但是对于jvm来说仍然可能会发生错误,在JDK1.5之后,双重检查锁定才能够正常达到单例效果 * * @author :liuqi * @date :2018-06-13 15:26. */ public class SingletonDemo4 { private static SingletonDemo4 instance; private SingletonDemo4() { } public static synchronized SingletonDemo4 getInstance() { if(instance == null){ synchronized (instance){ if (instance == null){ instance = new SingletonDemo4(); } } } return instance; } }
这样避免了进入synchronized块所需要花费的资源,其次如果两个线程同时进入了第一个if判断,那么他们也必须按照顺序执行synchronized块中的代码,第一个进入代码块的线程会创建一个新的Singleton实例,而后续的线程则因为无法通过if判断,而不会创建多余的实例。上述描述似乎已经解决了我们面临的所有问题,但实际上,从JVM的角度讲,这些代码仍然可能发生错误。
对于JVM而言,它执行的是一个个Java指令。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就使出错成为了可能,我们仍然以A、B两个线程为例:
1. A、B线程同时进入了第一个if判断
2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
于是我们想到了通过内部类实现多线程环境中的单例模式
懒汉式第四种:无参构造方法,静态内部类
/** * 单例模式:懒汉式,第四种,无参构造方法,静态内部类 * * @author :liuqi * @date :2018-06-13 14:15. */ public class SlackerSingletonNoParamStructure { /** * 私有构造方法 */ private SlackerSingletonNoParamStructure() { } /** * 创建内部类 */ private static class ExampleChild { // 内部类加载时初始化一次 private static final SlackerSingletonNoParamStructure newInstance = new SlackerSingletonNoParamStructure(); } /** * 提供公有访问函数 * * @return SlackerSingletonNoParamStructure */ public static SlackerSingletonNoParamStructure getInstance() { // 调用内部类 进行初始化对象操作 return ExampleChild.newInstance; } /** * 测试 * * @param args */ public static void main(String[] args) { // 新建5个线程,测试getInstance方法是否获取同一个SlackerSingletonNoParamStructure对象 for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println(SlackerSingletonNoParamStructure.getInstance()); } }).start(); } } }
懒汉式第五种:有参构造方法,静态内部类
/** * 单例模式:懒汉式,第五种,有参构造方法,静态内部类 * * @author :liuqi * @date :2018-06-13 14:42. */ public class SlackerSingletonParamStructure { /** * 私有构造方法:有参 */ private SlackerSingletonParamStructure(String str) { System.out.println(str); } /** * 创建内部类 */ private static class ExampleChild { // 内部类加载时初始化一次 private static final SlackerSingletonParamStructure newInstance = new SlackerSingletonParamStructure("testInfo"); } /** * 提供公有访问函数 * * @return SlackerSingletonParamStructure */ public static SlackerSingletonParamStructure getInstance() { // 调用内部类 进行初始化对象操作 return ExampleChild.newInstance; } /** * 测试 * * @param args */ public static void main(String[] args) { // 新建5个线程,测试getInstance方法是否获取同一个SlackerSingletonParamStructure对象 for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println(SlackerSingletonParamStructure.getInstance()); } }).start(); } } }
饿汉式第二种:静态内部类
/** * 单例模式:饿汉式写法,静态内部类 * * @author :liuqi * @date :2018-06-13 15:00. */ public class HungrySingleton { /** * 程序启动时就加载创建对象 */ private static final HungrySingleton newInstance = new HungrySingleton(); /** * 私有构造方法 */ private HungrySingleton() { } /** * 提供公有访问函数 * * @return Example */ public static HungrySingleton getInstance() { return newInstance; } /** * 测试 * * @param args */ public static void main(String[] args) { // 新建5个线程,测试getInstance方法是否获取同一个HungrySingleton对象 for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println(HungrySingleton.getInstance()); } }).start(); } } }
参考:https://www.cnblogs.com/jingpeipei/p/5771716.html
https://www.cnblogs.com/Ycheng/p/7169381.html
http://www.private-blog.com/2017/11/16/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8D%95%E4%BE%8B/
代码地址:https://github.com/yuki9467/TST-javademo/tree/master/src/main/singleton