设计模式:单例模式与线程安全
模式说明
单例模式,顾名思义,JVM全局下某Class只有一个实例对象。
使用场景
一个全局使用的类避免频繁地创建与销毁,节省系统资源。
理解重点
- 构造函数私有
- 统一访问入口,public staic 修饰
- 实例创建过则返回,没有则创建
- 线程安全:synchronized、volatile、双重检查;基于类初始化
线程不安全
public class SingleMan {
/**
* 私有构造函数
*/
private SingleMan() {
}
/**
* 实例化单例对象
*/
private static SingleMan singleMan;
/**
* public static 修饰
*
* @return
*/
public static SingleMan getInstance() {
if (singleMan == null) {
singleMan = new SingleMan();
}
return singleMan;
}
}
多线程执行getInstance()时,可能会造成两种情况:
- 实例在进程下可能有多份(虽然对外还是一个)
- 一个线程可能拿到一个未经初始化完的单例对象
线程安全
版本1 - synchronized
对于以上线程不安全版本,可以对getInstance()加synchronized修饰
这里代码不写了,虽然保证了线程安全,但每次获取单例时性能差。
版本2 - 双重检查
public class SingleMan {
/**
* 私有构造函数
*/
private SingleMan() {
}
/**
* volatile 修饰
* 多线程赋值时,要求有序性、可见性
*/
private static volatile SingleMan singleMan;
/**
* public static 修饰
*
* @return
*/
public static SingleMan getInstance() {
if (singleMan == null) {
// 加锁
synchronized (SingleMan.class) {
// 双重检查,可能执行到这时,被上一个线程赋值过了
if (singleMan == null) {
singleMan = new SingleMan();
}
}
}
return singleMan;
}
public static void main(String[] args) {
new Thread(() -> {
System.out.println(SingleMan.getInstance());
}).start();
new Thread(() -> {
System.out.println(SingleMan.getInstance());
}).start();
}
}
- synchronized 从修饰方法放到方法内修饰代码块,不为null直接返回,提高性能。
- 多线程下的双重检查。
- volatile 修饰,保证有序性(使得线程拿到初始化完整的单例对象)和可见性。
版本3 - 类初始化
基于Java 对类的初始化的唯一性,同时满足延迟初始化,比较巧妙。
public class SingleMan {
/**
* 私有构造函数
*/
private SingleMan() {
}
/**
* private static 修饰
* JVM 保证类的初始化的线程安全
*/
private static class InstanceHolder {
public static SingleMan instance = new SingleMan();
}
public static SingleMan getInstance() {
// 这里才是 InstanceHolder 类的初始化
return InstanceHolder.instance;
}
}
参考
- 《Java并发编程的艺术》