单例模式,是一种经典的设计模式,在校招中最常考的设计模式之一。设计模式就像是软件开发中的棋谱,是大佬们针对一些常见场景,总结出来的代码的编写套路~~
目录
一、设计模式的概念
什么是设计模式:
设计模式好比象棋中的 "棋谱".,红方当头炮, 黑方马来跳, 针对红方的一些走法, 黑方应招的时候有一些固定的套路,按照套路来走局势就不会吃亏
就是大佬们总结的一些固定的套路,按照这个套路来实现代码,也不会吃亏
二、单例模式的概念
单例 => 单个实例(instance)对象。类的实例,就是对象,单例就是一个程序中,某个类,只创建出一个实例(一个对象),不能创建多个对象。
Java中的单例模式,借助Java语法,保证某个类,只能够创建出一个实例,而不能new多次,一般单例模式分为懒汉模式和饿汉模式~~
2.1懒汉模式(从容)
那么懒汉模式是什么?比如说,吃完饭之后需要洗碗,如果是懒汉模式,就会把碗放到一边,等到下一顿吃的时候,需要用到碗了再洗~~用几个碗洗几个碗~~
2.2饿汉模式(急迫)
那么饿汉模式又是什么?比如说,吃完饭之后需要洗碗,饿汉模式就会立即去洗碗~~全都洗了~
2.3计算机中的例子
打开一个硬盘上的文件,读取文件内存,并显示出来;
饿汉:把文件所有内容都读取到内存中,并显示
懒汉:只把文件读一小部分,把当前屏幕填充上,如果用户翻页了,再读其他文件内容,如果不翻页,就省下了~
假设文件非常大,饿汉可能要打开半天,懒汉就可以快速打开~
三、饿汉模式代码实现
//把这个类设定成单例的~~
class Singleton{
//唯一实例的本体
private static Singleton instance = new Singleton();
//获取实例的方法
public static Singleton getInstance(){
return instance;
}
//禁止外部 new 实例
private Singleton(){
}
}
唯一实例对象,被static修饰,该属性是类的属性,JVM中,每个类的类对象只有一份~~
此处需要把new操作禁止掉!!设为private即可!!
四、懒汉模式代码实现
class SingletonLazy{
private static SingletonLazy instance = null;
public static SingletonLazy getInstance(){
if (instance == null){
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy(){
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
}
}
只需要在调用方法的时候才创建对象~~
五、懒汉模式和饿汉模式的线程安全问题
只是单纯的读操作,不是修改操作,不会触发线程安全问题!!!所以是安全的
如果多个线程下调用getInstance,懒汉模式就会出现问题,可能无法保证创建对象的唯一性!
假如N个线程,同时调用getInstance方法,可能就搞出N个对象了!!!
那么如何解决这种安全问题?
5.1解决方法一
加锁~~~
保证判定和new是一个原子操作
加锁其实是一个比较低效的操作(加锁就可能涉及到阻塞等待),非必要,不加锁~~~
5.2解决方法二
也可以保证判定和new是一个原子操作,但是任何时候调用getInstance都会触发锁的竞争~~
其实此处的线程不安全,只出现在首次创建对象这里,一旦对象new好了,后续调用getInstance,就只是单纯的读操作,就没有线程安全问题,就没必要再加锁了!!
5.3解决方法三
双重if判断,这俩if条件目的是不同的,这俩个条件看起来代码一样,实际上他俩的执行时机,差别很大,因为if中间隔了个synchronized,加锁可能导致阻塞,什么时候解除阻塞,沧海桑田~~
第一个if为了判断是否需要加锁
第二个if为了创建对象
虽然俩个条件相同,但是如果调用的时间间隔长了,结果也可能会不同
六、懒汉模式指令重排序问题
代码中可能还会触发指令重排序问题~~
俩个线程同时调用getInstance方法时:
这个操作可能会触发指令重排序~~
1.创建内存(买房子)
2.调用构造方法(装修)
3.把内存地址,付给引用(拿到钥匙)
当指令这样执行时:
此时系统调度给t2了,再去判断条件,发现条件不成立,非空,直接返回实例的引用,接下来t2继续调用后续方法,可能就都是将错就错了!!!
因为t2还是个没装修的毛坯房
当然为了解决这个问题,加上volatile关键字就会避免
当然这个过程也是个小概率事件~~
七、总结单例模式
单例模式,线程安全问题:
饿汉模式:天然就是安全的,只有读操作
懒汉模式:不安全的,有读也有写
1.加锁:把if和new变成原子操作
2.双重if:减少不必要的加锁操作
3.使用volatile禁止指令重排序,保证后续线程拿到的是完整对象