简介
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
饿汉模式
饿汉模式:顾名思义就是在程序启动后,立即创建出实例.
class Singletion {
private static Singletion instance = new Singletion();
// 类方法
public static Singletion getInstance() {
return instance;
}
// 做出限制,禁止通过new对象来创建实例, 但是有例外,反射可以创建出实例,但不推荐使用
private Singletion() {
}
}
public class Demo17 {
public static void main(String[] args) {
Singletion s1 = Singletion.getInstance();
Singletion s2 = Singletion.getInstance();
System.out.println(s1 == s2);
}
}
答案是true,说明获取的确实是同一个对象.
// 此处是在Singletion类会在第一次在jvm使用时加载,也不是只在程序启动的时候.
// 进入jvm后立即创建实例
private static Singletion instance = new Singletion();
当我们不使用getInstance方法来回去实例的时候,而采用new对象的方法,就会报错.
饿汉模式比较简单,这里就不多赘述了,接下来让我们看懒汉模式.
懒汉模式
懒汉模式:顾名思义就是只在第一次创建实例的时候,创建实例,能不创建就不创建.
class Singletionlazy {
private static Singletionlazy instance = null;
public static Singletionlazy getInstance() {
// 只有当没有实例的时候,才会new出一个实例
if(instance == null) {
instance = new Singletionlazy();
}
return instance;
}
// 禁止new出实例
private Singletionlazy() {
}
}
public class Demo18 {
public static void main(String[] args) {
Singletionlazy s1 = Singletionlazy.getInstance();
Singletionlazy s2 = Singletionlazy.getInstance();
System.out.println(s1 == s2);
}
}
结果为: true.
可是仔细观察,饿汉模式和懒汉模式是不是哪个或者全部有问题呢?
相信你们也能看出来.懒汉模式是线程不安全的!!
在饿汉模式中,通过getInstance方法只return instance对象就可以了.
但是在懒汉模式中,还有一步new 对象的操作,多线程进行肯定时不安全的.
我画个图供赏析.
那我们如何解决这个问题呢?
加锁
public static Singletionlazy getInstance() {
// 只有当没有实例的时候,才会new出一个实例
synchronized (Singletionlazy.class) {
if(instance == null) {
instance = new Singletionlazy();
}
}
return instance;
}
这里虽然解决了new多个对象的问题,但是加锁是一个成本比较高的操作,加锁可能会引起阻塞等待.如果无脑加锁,就会使执行效率收到影响.
我们加锁了,但是后面调用的时候,都会加锁,这样很影响程序的效率.
我们说的懒汉模式线程不安全,主要是在第一次new对象的时候,后续根据if就不会进行new对象了,也就不再需要锁了.
调整加锁
public static Singletionlazy getInstance() {
// 只有当没有实例的时候,才会new出一个实例
if (instance == null) {
synchronized (Singletionlazy.class) {
if(instance == null) {
instance = new Singletionlazy();
}
}
}
return instance;
}
我们在加锁之间加一个if判断,当instance为空的时候,才加锁.
这样既没有线程安全问题,程序效率也提升了.
是不是以为已经没有问题了…
考虑到加锁操作可能会阻塞,阻塞多久我们也不知道,有可能Instance此时就被更改了!!
就又出现了线程安全问题!!
也就是内存可见性问题,也是引起线程不安全的问题之一.
内存可见性和指令重排序
仔细分析,这里会不会出现内存可见性问题,只是会有这样的风险,编译器是否会采取优化,不好说,所以我们加上volatile是保险操作.
private volatile static Singletionlazy instance = null;
此处volatile还有另外一个作用,指令重排序问题.
// 可以分为3步
instance = new Singletionlazy();
- 给对象创建出内存空间.得到内存地址.
- 调用构造方法,对对象进行初始化.
- 把内存地址赋给Instance引用.
如果我们在第2步之前进行第3步,还没有给对象初始化,就调度给别的线程了,
我们再利用这个对象调用方法,属性的时候,因为没有初始化,就会出现问题,
加入volatile后,就解决了这个问题.
结语
我总结了单例模式以及其中的饿汉和懒汉模式,其中懒汉模式是线程不安全的,我们需要通过加锁,双层if操作,以及volatile来解决线程安全问题.
大家多多支持,我会火力更新下一篇的->阻塞队列及实现和定时器.