单例模式
什么是单例模式
单例模式确保了一个系统中一个对象只存在一个实例,这样可以避免一个全局使用的类过于频繁的创建与销毁。
单例模式的实现
单例模式特点,不对外提供构造方法,私有化构造器。
饿汉式
饿汉式主要体现在饿上面,就像一个自助餐厅,饿了进去就可以直接开整,不需要等待。
代码实现:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance(){
return singleton;
}
}
懒汉式
由于饿汉式不管你是否需要,我先创建,会导致浪费空间,所以有了懒汉式。
懒汉式主要体现在懒上,属于临时抱佛脚类型,使用的时候才会实例化。
代码实现:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
这种方式创建在单线程下面是可以的,但是在多线程的时候会出现问题,当第一个线程进入if但未进行实例化的时候,第二个线程过来,这个时候会创建多个实例。
这时我们就考虑得给这个对象加一个锁.
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
这样加锁会发现还是会面临上面的问题,在第一次线程进入if后,第二个线程也进来了,这时第一个线程才去执行下面的代码,当第一个线程执行完过后第二个线程依旧会创建一个实例。所以我们考虑再上一次判断,这就变成了双重校验锁了
双检锁/双重校验锁
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
这样就OK了吗?这样在多线程下还是会出现问题,在JVM中singleton = new Singleton() 并不是一个原子操作,它会被分解成三步
- 分配内存空间
- 初始化对象
- 将地址指向分配的空间
然而在jvm中可以对以上顺序进行优化,有可能会将第二,三部的执行顺序交换。这时就会出现问题,在第一个线程进入锁先执行了1->3,这个时候第二个线程进行判断,发现该对象指向的地址不为空,返回这个未初始化的对象。
针对这种情况,我们可以加上一个volatile关键字。
volatile可以保证可见性,但不保证原子性。还可以禁止指令重排。
所以最终的代码是:
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}