前言
Java其实有23种设计模式,这里简单写一下单例模式,单例模式主要是保证该类在实例化的时候,最多只生成一个实例,按照类型,可以分为懒汉式和饿汉式。
一、饿汉式
顾名思义,饿汉式是指的该类还没有被调用的时候就初始化实例,调用的时候直接拿出来,下面我们先简单写一个例子:
//饿汉式
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton(){
}
public static Singleton getSingleton(){
return singleton;
}
}
饿汉式由于直接实例化,所以在多线程的场景中并不存在线程安全的问题,缺点就是类未被调用的时候就初始化实例,会造成内存资源的浪费。
二、懒汉式
顾名思义,懒汉式就是突出一个懒字,他是在被调用的时候才不情不愿的初始化一个实例出来,下面我们先简单写一个例子:
代码如下:
public class Singleton {
private static Singleton singleton;
private Singleton(){};
public Singleton getSingleton(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
这样的方式在单线程的场景下是没有问题的,只会生成一个实例,但是如果是在多线程的场景下,就会存在问题,比如:
A线程判断singleton==null为true,这个时候他会进入判断里面执行new Singleton();而就在执行这个语句前,好巧不巧,刚好B线程也来凑热闹,首先也是判断 singleton是否是null,由于这个时候A线程的初始化实例还没开始,所以B线程也会进来实例化一个对象,这样就违背了我们该类只能初始化一个实例的初衷,这个时候我们改进一下代码:
public class Singleton {
private static Singleton singleton;
private Singleton(){};
public Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
在这里示例中,我们加入了synchronized 锁,这样就能保证B线程一定是在A线程实例化对象后再执行,但是我们也能意识到这样的代码和之前的会遇到相同的问题,虽然加了锁,但是B线程等待锁执行完成后,还是会再初始化一个实例出来,所以我们这里还得再加一个二次校验。
public class Singleton {
private static Singleton singleton;
private Singleton(){};
public Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这样看起来,我们的程序写的好像没什么问题了,但是在多线程的场景下,还会涉及到一个问题,那就是共享变量对不同的线程是不可见的,只有等修改后的共享变量刷新到主内存,该变量才会对其他线程可见。所以我们这里A线程执行了new Singleton()实例化一个对象,但此时,针对B线程来说,他不会立马知道已经初始化成功了一个对象,此时他还是会去实例化一个新的对象,所以针对这个问题,Java为我们提供了一个关键字volatile ,我们来写一下最终版示例:
public class Singleton {
private static volatile Singleton singleton;
private Singleton(){};
public Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Singleton 被volatile修饰后,该变量只要被修改,会里面写入主内存,对其他线程可见,所以当A线程执行了new Singleton()操作,B线程这个时候就知道该类已经实例化了,就不会再new新的实例了。
总结
饿汉式:在多线程场景下不会存在线程安全问题,但是由于一开始就初始化实例,会造成内存资源的 浪费。
懒汉式:在多线程场景下会存在线程安全问题,但是我们通过引入synchronized锁和volatile关键字来解决该方式下的线程安全问题。