什么是单例模式
- 首先我们先了解一下什么是单例模式,其实单例模式是创建类型的一种常用的软件设计模式,也是我们多线程编程中用的最多,最重要的一种设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
- 其实最简单的理解就是顾名思义,单例模式就是只有一个实例化对象。
单例模式的实现方式
-
饿汉模式
饿汉模式就是再类加载的时候就把对象创建好,不管用不用都会被创建
class Test{ //单例模式之饿汉模式
private static Test t = new Test();
private Test() {} //把构造方法私有化,禁止类外部
private static Test getTest(){
return t;
}
}
-
懒汉模式
懒汉模式就是什么时候调用,什么时候创建,相比于饿汉模式效率比较高,不会浪费空间内存
class Test2{ //单例模式之懒汉汉模式
private static Test2 t = null;
private Test2() {}
private static Test2 getTest(){
if(t == null) {
t = new Test2(); //只有第一次调用getTest才会new一个新对象,之后再调用就会直接返回第一次创建好的。
}
return t;
}
}
-双检模式
public class Singleton {
public static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
//采用两个if可以解决第二次调用的时候会触发获取锁的操作;
if (instance == null) {
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
- 我们之前已经了解过饿汉模式和懒汉模式了,那我们的双检模式和前两个模式的区别就在于多了一个synchornized代码块,然后多了两个 if 判断语句,多了个volatile关键字、
synchornized代码块
首先我们说为什么要用synchornized锁,当有两个线程同时执行了getIntance方pan创建对象的时
候,这个时候intance是null,然后两个线程就会同时new出来两个新对象,然后返回,这就失去了我们
单例模式的意义。当我们加上锁之后,当第一个线程正在执行获取到锁的时候,线程2就会等待,等线
程1结束的时候,线程2就会获取到锁,然后判断intance不为空,就会返回线程1已经创建好的对象,**
这就保证了多线程并发执行getInstace方法的时候,仍然能保证是单例模式**;
双if语句(双检)
然后我们说为什么要用两个 if 语句,synchornized锁里面 if 语句我们已经知道作用了,防止重复创建
实例化对象。如果我们不用synchornized外面的 if ,那么程序每次都会获取锁,释放锁,然后再判断
instance是否为空,程序每次释放锁,获取锁也是会很耗费系统的资源,影响程序的效率,再加一个 if
的话,当instance不为空,也不用获取锁,会直接返回之前创建好的。**这样做能避免程序进行无意义
的获取释放锁**,极高的提高程序的执行效率。
volatile关键字
我们之所以在单例模式中使用volatile,是为了解决jvm指令重排序带来的问题。并不是为了解决内存可见性问题,synchornized已
经为我们解决了内存可见性问题。
例如 instance = new Singleton();这个操作就不是原子性的,它的执行过程如下:
- 给instance分配内存
- 调用Singleton的构造函数进行初始化
- 将instance对象指向分配的内存空间
经过我们JVM优化之后,顺序就可能变成1,3,2,即先将instance指向未初始化完成的内存空间,只有执行完第三步之后,
instance才不为空
如此此时线程A先将instance指向未初始化完成的内存空间,然后线程B执行getInstance方法,获取到instance不为空返回,此时
获取到的instance就是未初始化完成的,调用的时候就会出错。
单例模式的作用
我们实现单例模式的目的是为了什么,为了让程序中只有一个实例化对象(比如某个大型服务器中存了大量的数据,我们的服务
器程序不想让系统中存在太多这样重复的大对象),之所以设计这种模式,是因为如果我们手动控制让一个类只被new一次,不
被重复new,要靠程序员手工去实现,但是这样是不太靠谱的。而我们实现了单例模式后,单例模式就可以让编辑器自动帮我们
做更充分的检查,类似于编辑器会自动检查final,abstract关键字一样。