一、含义
单例模式只能有一个实例,为了保证这个可以实现,将构造方法私有化,不让外界随便创建对象。私有化构造方法后就是把new这个对象控制权收回了,只能在类内部去实例化这个对象,让类自身负责保存他的唯一实例。
二、两种实现方式
2.1饿汉式
特点:还没有使用就已经创建好对象,以空间换时间,线程安全
public class Singleton1 {
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
2.2懒汉式
特点:用到时再创建对象,以时间换空间,线程不安全
public class Singleton2 {
private static Singleton2 INSTANCE;
private Singleton2() {}
public static Singleton2 getInstance() {
if(INSTANCE == null) {
INSTANCE = new Singleton2();
}
return INSTANCE;
}
}
但这样创建懒汉式肯定是不行的,如果多个线程同时访问,那就会创建很多对象,所以要加以控制。可以使用synchronized
public static synchronized Singleton2 getInstance() {
if(INSTANCE == null) {
INSTANCE = new Singleton2();
}
return INSTANCE;
}
这样还是不行,大家其实也知道为了保证线程安全效率会降低。而懒汉式本来只有第一次创建对象的时候才会产生线程不安全的情况,直接给整个方法加锁会降低效率,可以考虑用双重检测
public class Singleton2 {
private static Singleton2 INSTANCE;
private Singleton2() {}
public static Singleton2 getInstance() {
if(INSTANCE == null) {
synchronized (Singleton2.class) {
if(INSTANCE == null) {
INSTANCE = new Singleton2();
}
}
}
return INSTANCE;
}
}
如果不在里面加判断的话,如果线程A先进去并拿到锁,但还未创建对象。此时B来了,它通过了第一次判断,但没锁在外面等待,A创建完对象之后B再进去创建,这岂不是就错了,所以要判断两次。
这里可以引入一个概念:volatile,想要利用它的特性——禁止指令重排。
指令重排:为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,并确保这一结果和顺序执行结果是一致的。但是这个过程并不保证各个语句计算的先后顺序和输入代码中的顺序一致,这就是指令重排序。简单来说,就是指你在程序中写的代码,在执行时并不一定按照写的顺序。
为什么要提这个呢,在单线程时不会有问题,但如果多个线程并发的,就有可能出错,所以可以在定义的时候加上这个关键字来避免这种情况发生。
public class Singleton2 {
//volatile是Java提供的一种轻量级同步机制
//特点:1、保证可见性 2、不保证原子性 3、保证有序性,禁止指令重排
private volatile static Singleton2 INSTANCE;
private Singleton2() {}
public static Singleton2 getInstance() {
if(INSTANCE == null) {
synchronized (Singleton2.class) {
if(INSTANCE == null) {
INSTANCE = new Singleton2();
}
}
}
return INSTANCE;
}
}
其实我们还有一种方法可以解决懒汉式的线程安全问题,可以利用jvm对静态变量的线程安全管理——内部类懒汉式
public class Singleton5 {
private Singleton5(){}
private static class Holder {
//由jvm来保证线程安全
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
}