一. 概述
1. 什么是单例模式?
单例模式:为确保一个类只能有一个实例,在这个类中创建自己唯一的实例
2. 为什么用单例模式?
许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为,按照单例模式设计类就可以提供这样的全局对象,以避免调用对象不一致的情况。例如线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序等对象常被设计成单例。
3. 单例模式的要素?
- 私有的构造器
- 指向自己实例的私有静态引用
- 返回自己唯一实例的公有静态方法
二. 单例模式
1. 饿汉式(线程安全)
在单例类加载时就实例化一个自己的对象
public class Singleton {
//1.私有构造器
private Singleton(){}
//2.私有静态引用
private static SingletonTest single = new SingletonTest();
//3.共有静态方法
public static SingletonTest getInstance(){
return single;
}
}
2. 懒汉式(线程不安全)
在其他类调用该单例类的方法来获取实例时才实例化一个自己的对象
public class Singleton {
//1.私有构造器
private Singleton(){}
//2.私有静态引用
private static Singleton single;
//3.共有静态方法
public static Singleton getInstance(){
if(single == null) { //这里可能由于线程阻塞造成了多个线程都判断为null,结果造出多个对象,导致线程不安全
single = new Singleton();
}
return single;
}
}
懒汉式线程不安全问题解决方法:
- 静态方法前加 synchronized 关键字, 同步方法(作用域大,执行效率低)
- 静态方法里加 synchronized 同步代码块(执行效率仍低)
- 私有静态内部类,延迟加载(优于1和2方法)
public class Singleton {
private static class Holdon{
private static Singleton single = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return Holdon.single;
}
}
3. 双重检测锁(DCL,即double-checked locking)
public class Singleton {
private Singleton(){}
private static volatile Singleton single;
public static Singleton getInstance(){
if(single == null){
synchronized (Singleton.class){
if(single == null){
single = new Singleton(); //这一语句并非原子性操作
}
}
}
return single;
}
}
为什么必须要用 volatile 修饰 single?
single = new Singleton();这条语句在 JVM 进行了三个操作:
- 分配内存空间给对象
- 初始化对象
- 把对象的内存地址赋给引用变量single
这个过程发生的顺序是无序的,当出现了以下情况就会出现问题:
- 线程1正常进入执行 single = new Singleton();
- 分配内存空间给对象
- 内存地址赋给single变量
- 线程2进入,判断此时的single不为null,返回一个未初始化的对象
- 线程1初始化对象
这样线程2就获取了一个不完整的对象,造成一系列错误导致最终程序崩溃
volatile关键字 有内存屏蔽的功能,可以避免这一情况的发生
4. 枚举实现单例
public enum Singleton(){
INSTANCE;
public void whateverMethod(){
}
}