单例设计模式,意味着整个系统中只能存在一个实例,比方说像日志对象这种。咱们常说的有饿汉式和懒汉式这两种模式来建立单例对象,今天就拓展一下思惟,多看几种。html
首先咱们如果想一个类只有一个对象,那确定先要私有化构造器,断了在其它的类中使用构造器建立实例的念头。其它的类中不能建立,咱们就只能在类中本身建立一个私有实例,另外还要提供一个共有的方法使其它对象获取到实例。因此,初版出现了。设计模式
1 【饿汉式 V1】安全
在类加载的时候就建立实例bash
@ThreadSafe
public class SingletonExample2 {
//
私有化构造器
private SingletonExample2(){}
//
提供一个实例
private static SingletonExample2 instance = new SingletonExample2();
//
提供共有的方法返回实例
public static SingletonExample2 getInstance(){
return
instance;
}
}
|
不要忘了在多线程环境中还有关注线程是否安全,我这里都会打上注解,@ThreadSafe 表示线程安全,@NotThreadSafe 表示线程不安全。多线程
上面这种方式就是比较简单的,也是最容易想到的方式,就有一个缺点,如果不使用这个对象,那就有点浪费资源了,这个对象不必定会被使用,可是咱们已经建立好了。性能
2 【饿汉式 V2】优化
这种方式是借助于 “静态代码块只会被加载一次” 来实现单例的建立,很简单,也很好理解,问题和饿汉式同样,不必定就会使用到这个对象,因此可能会出现浪费资源的状况。spa
@ThreadSafe
public class SingletonExample6 {
//
私有化构造器
private SingletonExample6(){}
private static SingletonExample6 instance = null;
static {
instance = new SingletonExample6();
}
//
提供共有的方法返回实例
public static SingletonExample6 getInstance(){
return
instance;
}
}
|
3 【懒汉式 V1】线程
在对象使用的时候才建立实例设计
@NotThreadSafe
public class SingletonExample1 {
//
私有化构造器
private SingletonExample1(){}
//
提供一个实例
private static SingletonExample1 instance = null;
//
提供共有的方法返回实例
public static SingletonExample1 getInstance(){
if
(instance == null){
return
new SingletonExample1();
}
return
instance;
}
}
|
这种方式在单线程的时候是没有问题的,可是在多线程时就会出现问题,假如线程 A 进入 if 以后暂停执行,此时又来一个线程 B 仍是能够进入 if 并返回一个实例,此时 A 再次得到执行时,返回的是另外一个实例了。
4 【懒汉式 V2】
在共有方法上添加 synchronized 关键字,同步该方法。可行,可是不推荐使用,由于 synchronized 修饰方法以后,在同一时刻只能有一个线程执行该方法,一旦有线程得到方法,其它线程须要等待,这样会浪费大量时间,系统运行效率下降。
@ThreadSafe
@NotRecommend
public class SingletonExample3 {
//
私有化构造器
private SingletonExample3(){}
//
提供一个实例
private static SingletonExample3 instance = null;
//
提供共有的方法返回实例
public static synchronized SingletonExample3 getInstance(){
if
(instance == null){
return
new SingletonExample3();
}
return
instance;
}
}
|
5 【懒汉式 V3】
这种方式使用双重检测 + 防止指令重排的方式来保证线程安全,首先须要注意的是在 getInstance 方法中,咱们须要双层检测并使用同步代码块将建立对象的过程同步起来。
@NotThreadSafe
public class SingletonExample4 {
//
私有化构造器
private SingletonExample4(){}
//
提供一个实例
private static SingletonExample4 instance = null;
//
提供共有的方法返回实例
public static SingletonExample4 getInstance(){
//
线程 B 判断,发现 instance 不为空,直接返回,而实际上 instance 尚未初始化。
if
(instance == null){
//
双重检测机制
synchronized (SingletonExample4.class) {
//
同步锁
if
(instance == null){
//
线程 A 执行到重排后的指令 3 ,此时 instance 已经有地址值了。可是没有初始化
return
new SingletonExample4();
//
这里是重点!!
}
}
}
return
instance;
}
}
|
由于在 new SingletonExample4() 的过程当中,并非一个原子操做,是能够进一步拆分为:
一、分配对象内存空间
memory = allocate()
二、初始化对象
initInstance()
三、设置 instance 指向刚分配的内存
instance = memory
在多线程的状况下,上面 3 个指令会存在指令重排序的状况。【JVM 和 CPU 指令优化】重排后的结果可能为:
memory = allocate()
instance = memory
initInstance()
此时可能会存在线程 A 在内层 if 执行到指令重排后的第 3 步,但并未初始化,只是存在了地址值,线程 B 在外层 if 判断时,会直接 return 实例,而这个实例是一个只有地址值而没有被初始化的实例。
为了防止指令重排带来的问题呢,咱们就可使用 volatile 关键字防止指令重排。这样就是线程安全的了。只需在上一版的基础上使用 volatile 修饰 instance 实例便可。
volatile 的语义就是添加内存屏障和防止指令重排,这在前面已经分析过了。
|
private static volatile SingletonExample4 instance = null;
6 【使用枚举类实现单例模式】
这是推荐使用的方法,由于它比懒汉式的线程安全更容易保证,比饿汉式的性能高,它只有在调用的时候才实例对象。
@ThreadSafe
@Recommend
public class SingletonSpecial {
private SingletonSpecial(){}
public static SingletonSpecial getInstance(){
return
Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
//
public static final Singleton INSTANCE;
private SingletonSpecial singleton;
//
JVM 来保证这个构造方法只会调用一次
Singleton(){
singleton = new SingletonSpecial();
}
public SingletonSpecial getInstance(){
return
singleton;
}
}
}
|
7 【使用静态内部类】
这种方式在 Singleton 类被装载时并不会当即实例化,而是在须要实例化时,调用getInstance方法,才会加载 SingletonInstance 类,从而完成 Singleton 的实例化。
使用 static final 修饰以后 JVM 就会保证 instance 只会初始化一次且不会改变。
@ThreadSafe
@Recommend
public class SingletonExample7 {
private SingletonExample7(){}
private static class SingletonInstance{
private static final SingletonExample7 instance = new SingletonExample7();
}
public static SingletonExample7 getInstance(){
return
SingletonInstance.instance;
}
}
|
总结一下,今天主要说了单例模式的实现,而且在这中间,复习了一下前面说的线程安全的应用。如果对线程安全的原理以及实现有不懂的能够回头看看前面几篇文章。
原文地址:https://www.cnblogs.com/YJK923/p/10516178.html