单例模式是应用的比较多的一种设计模式,也是面试时比较喜欢问的一种,这篇文章就带你学习一边单例模式的最基本的两种实现方式吧
饿汉式
顾名思义,饿汉式实现就是在类加载时就创建好了,不必等到调用获取实例方法的时候才创建对象,调用方法时直接返回就可以了。
public class HungrySingleton { // 一开始就获取了实例 private static HungrySingleton singleton = new HungrySingleton(); // 构造方法私有,外部无法构造 private HungrySingleton(){} // 公有向外提供 public static HungrySingleton getSingleton(){ return singleton; } }
测试方法
public class Test1 { public static void main(String[] args) { HungrySingleton s1 = HungrySingleton.getSingleton(); HungrySingleton s2 = HungrySingleton.getSingleton(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } }
HungrySingleton@16d3586 HungrySingleton@16d3586 true
由上面的输出结果可知确实实现了饿汉式单例模式。下面来看看懒汉式单例模式
懒汉式
这个也可以顾名思义,和饿汉式恰好相反,对象实例是在方法中才创建的。
public class LazySingleton { private static LazySingleton singleton = null; private LazySingleton(){} // 在方法里才实例化 public static LazySingleton getSingleton(){ if(singleton == null){ singleton = new LazySingleton(); } return singleton; } }
LazySingleton@16d3586
LazySingleton@16d3586
从测试的代结果中也可以看出,确实是获取了同一个对象,那么这两种模式有什么区别呢
1.懒汉式默认不会实例化,要等到外部调用方法时才会,饿汉式一开始就实例化了对象
2.线程安全上,饿汉式肯定是线程安全的,因为在线程没出现之前就实例化了对象,懒汉式则是线程不安全的,因为在多线程下,如果一个线程判断完实例为null就休眠或着中断,那么另一个线程也进入方法,判断实例也为null,那么该线程就会创建实例对象,而原来的那个休眠线程恢复以后,直接就执行实例化new对象这一步,那么就会出现多个实例。 下面来通过例子验证一下
public static void main(String[] args) { // 饿汉式 new Thread(()->{ System.out.println(HungrySingleton.getSingleton()); }).start(); new Thread(()->{ System.out.println(HungrySingleton.getSingleton()); }).start(); new Thread(()->{ System.out.println(HungrySingleton.getSingleton()); }).start(); }
饿汉式我开启了三个线程去获取对象,得到的都是同一个对象
HungrySingleton@1797c69
HungrySingleton@1797c69
HungrySingleton@1797c69
懒汉式下我开启了四个线程去获取对象,结果出现了一个与其它不同的对象,说明懒汉式确实是线程不安全的
LazySingleton@1797c69
LazySingleton@1ca79ec
LazySingleton@1ca79ec
LazySingleton@1ca79ec
那么有没有方法去实现线程安全的懒汉式单例呢,首先最容易的就是加锁了,用synchronized关键字
public static synchronized LazySingleton getSingleton() // 在get方法中添加synchronized关键字,我开了二十个线程去执行,结构得到的都是同一个对象 // 除了可以在方法头添加关键字,还可以在代码块里添加,具体的效果和这个几乎是完全一样
那么问题来了,这样是不是就解决懒汉式线程安全的问题了呢,熟悉锁机制的朋友应该知道这样会使程序的运行效率大打折扣。
3.分析上面的线程安全,接下来就是性能了,可能你已经想到了,饿汉式不需要加锁,执行效率高,懒汉式需要加锁,执行效率低
4.占用内存上,饿汉式不管你用不用到它的实例对象,他一开始就已经实例化在那里了,占据了内存空间,而懒汉式等到用的时候才实例化,不会浪费内存
那来个表格总结一下吧