单例设计模式
单例模式
单 :唯一 例:实例
某个类在整个系统只有一个实例对象,可以获取和使用的代码模式;
要点:
-
某个类只能有一个实例
- 构造器私有化
-
必须自行创建这个实例
- 含有一个该类的静态变量来保存这个唯一实例
-
对外提供获取该实例对象方式
- 直接暴露
- 用静态变量get方法获取
-
饿汉式:直接创建对象,不存在线程安全问题
- 直接实例化饿汉式(简洁直观)
- 枚举式(最简洁)
- 静态代码块饿汉式(适合复杂实例化)
- 懒汉式:延迟创建对象
- 线程不安全(适用于单线程)
- 线程安全(适合于多线程)
- 静态内部类形式(适用于多线程)
饿汉式直接创建
直接创建实例对象,不管是否需要直接创建
- 构造器私有化
- 自行创建,并且静态变量保存
- 向外提供这个实例
- 强调单例,用fina修改
public class Singleton {
/**
* 该函数限制用户主动创建实例
*/
private Singleton() {}
private static final Singleton singleton = new Singleton();
/**
* 获取Singleton实例,也叫静态工厂方法
* @return Singleton
*/
public static Singleton getInstance() {
return singleton;
}
}
适用静态代码块
public class Singleton{
public static final Singleton INSTANCE;
static {
INSTANCE = new Singleton();
}
private Singleton(){}
}
枚举
枚举类型,表示该类型的对象是有限几个
可以限定一个,成就单列
public enum Singleton2{
INSTANCE
}
懒汉式
延迟创建这个实例对象
- 构造器私有化
- 用一个静态变量保存这个唯一实例
- 提供一个静态方法,获取这个实例对象
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
多线程不安全,加锁
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
return instance;
}
}
上面方式解决了线程问题,但是出现了并发性能问题,
比较差,应该做些优化,如果没有实例对象进行加锁创建,如果已经有了不需要加锁,直接获取实例。
双重检查 + lock方式
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null) { //线程A和线程B同时看到 instance不为空,直接返回对象
synchronized (Singleton.class) { //AB获取锁
if (instance == null) { //其中一个进入内部,另一个阻塞
instance = new Singleton();
}
}
}
return instance;
}
}
执行流程
- 第一个判断 ,如果instance不为空,则直接返回对象,不需要获取锁:而如果多线程发现instance为空,进入下一步,获取锁
- 多线程抢锁,只有一个线程成功,再次判断是否为空(因为可能被之前线程实例化了),再看看创建对象
- 其它线程看到不为空后,就不会获取锁了
引发另一个问题: 指令重排
使用volatile防止指令重排创建一个对象,在JVM中简化为三步过程:
- 为singleton分配内存空间
- 初始化singleton对象
- 让引用指向该分配好的位置
JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能
在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。
使用volatile关键字可以防止指令重排序,使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换,这样在多线程环境下就不会发生NPE异常了。
volatile还有第二个作用:使用volatile关键字修饰的变量,可以保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。
public class Singleton {
private static volatile Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null) { //线程A和线程B同时看到 instance不为空,直接返回对象
synchronized (Singleton.class) { //AB获取锁
if (instance == null) { //其中一个进入内部,另一个阻塞
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类方式 ,
保持多线程安全,和延迟加载效果
在内部类被加载和初始化时,才会创建instance实例对象,静态内部类不会自动随着外部类的加载和初始化而初始化,单独的去加载和初始化。因为内部类加载和初始化时,创建它,线程安全。
public class Singleton {
private Singleton(){
}
private static class Inner{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return Inner.instance;
}
}