单例模式是比较简单的设计模式。直接给出定义就明白了。
单例模式确保一个类只有一个实例,并提供一个全局访问点
要保证一个类只有一个实例需要做到以下几点:
- 私有类构造器,防止别的类直接用类构造器实例化实例
- 既然构造器私有了,那么只有类本身能够实例化。类需要提供一个公开的接口获取实例
- 类本身需要保证实例的单一
类图如下:
类怎么创建单一的实例,有以下几种办法:
错误的示范
public class Singleton {
private static Singleton instance;
//私有的构造器
private Singleton() {}
//公开的访问点
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面的代码看似符合单例模式,但是类本身无法保证只实例化一次实例。
在多线程的情况下,可能有多个线程得到 instance == null
为true的结果。
因此单例模式比较重要的就是怎么避免多线程重复实例化的问题。
使用 synchronized 同步
public class Singleton {
private static Singleton instance;
//私有的构造器
private Singleton() {}
//公开的访问点,加上synchronized避免多线程同时进入方法中
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
比较简单的方法,直接在实例方法上加synchronized
,两个线程无法同时进入方法,自然不会重复实例化了。
不好的是线程的互相等待影响效率。
实例化静态变量
public class Singleton {
//加载类时就创建对象,由jvm保证单例
private static Singleton instance = new Singleton();
//私有的构造器
private Singleton() {}
//公开的访问点
public static Singleton getInstance() {
return instance;
}
}
静态变量的实例jvm加载类时会自动创建,并且保证实例的唯一。
这个办法不好的地方是,即使不需要这个实例它也会创建。
双重检查加锁
public class Singleton {
//volatile保证不同线程之间的变量是一样的
private volatile static Singleton instance;
//私有的构造器
private Singleton() {}
//公开的访问点,没有实例化的前提下加锁
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
访问点在没有实例化的判断之后加锁,各个线程最多只有第一次获取实例的时候会被同步,提高了效率。
注意的是实例变量需要设置为 volatile。保证各个线程看到的全局变量一致。否则第二次的检查也有可能出现多线程错误。