Author:MTingle
major:人工智能
Build your hopes like a tower!
一、单例模式是什么?
单例模式是设计模式的一种,单例 = 单个实例,在一个Java进程中,要求特定的类,只能有唯一一个实例,通过特殊的技巧,来确保这里的实例不会有很多个(尝试 new 多个实例的时候,编译器会直接报错)
二.为什么需要有单例模式呢?
举个栗子,有的时候,代码中需要有一个对象来管理,持有大量数据,此时有一个对象就可以了,比如,一个对象管理了 10G 的数据,如果你不小心创建出了多个对象,内存空间机会成倍增长,机器就顶不住了.
那么如何确保对象是唯一的呢?
期望让机器(让编译器)能够对代码中的指定的类,创建的实例个数进行校验,如果发现创建多个实例了,就直接让编译器报错,此时就可以避免因失误创建出多个实例了.
在Java可以通过一些"奇技淫巧"来实现这个效果,此处介绍两种最基础的实现方式:懒汉模式与饿汉模式.
三、饿汉模式
java
class Singleton{
private static Singleton instance=new Singleton();
public static Singleton getInstance(){
return instance;
}
private Singleton(){
}
}
public class ThreadDemo26 {
public static void main(String[] args) {
// Singleton singleton1=new Singleton(); //报错
Singleton singleton=Singleton.getInstance();
}
}
private static Singleton instance=new Singleton();
static是静态的,指的是"类属性", instance 是 Singleton 类对象里面持有的属性, Singleton.class 从 .class 文件加载到内存中,表示这个类的一个数据结构,因此,instance指向的这个对象就是唯一一个对象
public static Singleton getInstance(){return instance;}
其他代码如果想要使用这个类的实例,就需要通过这个方法来进行获取,不应该在其它代码中重新 new 这个对象,而是通过这个方法获取到现成的对象
private Singleton(){ }
通过这个代码,其他代码就无法new了,从根本上不让其它人new出新的对象.只能使用getInstance获取.
上述代码就是"饿汉模式"的一种简单写法,而所谓的饿表示"非常急迫",实例是在类加载的时候就已经创建了,而创建的时机非常早,相当于程序一启动,实例就创建了,就使用"饿汉"形容"创建实例非常急迫 非常早".
并且 饿汉模式是线程安全的 ! ! !
四.懒汉模式
java
class SingletonLazy {
private volatile static SingletonLazy instance=null; // volatile: 防止指令重排序
public static Object locker=new Object();
public static SingletonLazy getInstance() {
if (instance==null) { // 如果instance==null,说明是首次调用,首次调用就需要考虑线程安全问题,就要加锁
//如果非null,就说明是后续调用,就不需要加锁了
synchronized (locker) {
if (instance == null) { //此处的if判断的是是否创建对象----双重校验锁
instance = new SingletonLazy();
}
}
}
return instance;
}
}
public class ThreaDemo27 {
public static void main(String[] args) {
}
}
懒汉模式 创建实例的时机不太一样,创建实例的时间只会更晚,知道第一次使用的时候,才会创建实例,顾名思义为"懒汉模式".
如果是首次调用 getInstance ,那么此时 instance 引用为 null ,就会进入 if 条件,从而把实例创建出来.如果是后续再次调用 getInstance ,由于 instance 已经不再是 null ,此时不会进入 if ,直接返回之前创建好的引用了.
这样的设定仍然可以保证,该类的实例是唯一一个,与此同时,创建实例的时机就不是程序驱动时了
懒汉模式是线程不安全的 ! ! !
因此,我们对他嵌套了多个 if 语句,具体功能如下
if (instance==null) { // 如果instance==null,说明是首次调用,首次调用就需要考虑线程安全问题,就要加锁
//如果非null,就说明是后续调用,就不需要加锁了
synchronized (locker) {
if (instance == null) { //此处的if判断的是是否创建对象----双重校验锁
instance = new SingletonLazy();
}
}
}
通过这两次加锁,我们还是不能完全保证线程是安全的,在代码中,我们还使用了关键字volatile ,这是为了防止指令重排序
JVM在执行new创建对象的时候会执行下面三条指令
1.分配内存空间
2.初始化对象
3.使变量指向对象
由于JVM进行指令重排,以上指令顺序可能发生变化,可能变成如下顺序
1.分配内存空间
2.使变量指向对象
3.初始化对象
假设现在有两个进程t1和t2
t1进程分配内存空间,使变量指向对象
t2进程此时调用该对象,会认为此时对象已经被初始化,但由于jvm的指令重排序,该对象并没有被初始化,此时就出现了线程不安全的状况!
注意了以上几点,我们创建出的懒汉对象就是线程安全的对象!