单例模式介绍
单例模式:单例模式属于工厂模式的特例,只是它不需要输入参数并且始终返回同一对象的引用。单例模式能够保证某一类型对象在系统中的唯一性,即某类在系统中只有一个实例。
单例模式中的两种模式介绍
1.懒汉模式:顾名思义,他是一个懒汉,不愿动弹。只有你主动叫他的时候他才会工作,也就是说实例在类加载的时候不被初始化,到了需要使用的时候才会进行初始化。
2.饿汉模式:顾名思义,他是一个饿汉,他很勤快就怕自己饿着。他总是先把食物准备好,什么时候需要吃了,他随时拿来吃,不需要临时去搞食物。因此饿汉模式下实例在类加载时就完成了初始化,但是加载比较慢,获取对象比较快。
懒汉模式代码实现
public class LazybonesSingleton {
//默认不会实例化,什么时候用就什么时候new
private volatile static LazybonesSingleton instance = null;
private LazybonesSingleton() {
}
public static LazybonesSingleton getInstance() {
if (instance == null) {
//什么时候用就什么时候new
synchronized (LazybonesSingleton.class) {
//使用双检查机制确保instance不为空时候只会被同步锁一次,减少使用锁的消耗,
//而且保证instance只会被new一次,
if (instance == null) {
instance = new LazybonesSingleton();
}
}
}
return instance;
}
}
LazybonesSingleton采⽤ volatile 关键字修饰也是很有必要的, instance = new LazybonesSingleton(); 这段代码其实是分为三步执行:
- 为
instance
分配内存空间 - 初始化
instance
- 将
instance
指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤ getInstance() 后发现 instance
不为空,因此返回instance
,但此时 instance
还未被初始化。
使⽤
volatile
可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏
饿汉模式代码实现
public class HungrySingleton {
//一开始类加载的时候就进行实例化,创建单实例对象
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
单例中懒汉和饿汉的区别
(1) 线程安全:饿汉式在线程还没出现之前就已经实例化了,所以饿汉式一定是线程安全的。懒汉式加载是在使用时才会去new实例的,那么你去new的时候是一个动态的过程,因此在new需要使用一些机制去保证创建实例时候的线程安全,例如上面代码所使用的同步锁+双检查机制。
(2)执行效率:饿汉式没有加任何的锁,因此执行效率比较高。懒汉式一般使用都会加同步锁,效率比饿汉式差。
(3)内存使用:饿汉式在一开始类加载的时候就实例化,无论使用与否,都会实例化,所以会占据空间,浪费内存。懒汉式什么时候用就什么时候实例化,不浪费内存。