在上文Java设计模式-单例模式的7种写法详解(上)记录的单例模式的,2种懒汉写法,2种饿汉写法,还有一种面试可能会问的错误的饿汉写法。本文继续记录后面三种都值得推荐使用的单例模式写法:双重检查单例模式,静态内部类单例模式,枚举单例模式。
笔记参考的开放视频资源:B站尚硅谷官方—尚硅谷图解Java设计模式韩顺平老师2019力作
前期知识:
volatile关键字:可理解为一个轻量级的synchronized ;语法上volatile只能用于修饰变量,可以使变量的修改立即更新到主存。
即:volatile保证了不同线程对该变量进行操作的可见性,即一个线程修改了,另一个线程立即可见
可利用这一特性配合同步代码块来实现单例模式
代码分析:双重检查单例模式是如何做到线程安全的呢?此外还有什么优点?
在多线程下,若多个线程同时获取实例,多个线程都进入了第一层if语句中if (instance == null) {},之后由于是同步代码块,只有一个线程进入,且instance变量被volatile关键字修饰,所以第一个线程将instance实例化后会立即更新到主存(因为volatile可以使变量的修改立即更新到主存),所以之后线程进入同步代码块后if (instance == null)雕件判断不成立,直接执行后面的 return instance;返回实例。
此外,在这种方式下instance实例化只执行一次,后面再调用getInstance方法时会直接返回instance。同样也是在调用getInstance方法时才会实例化的(需使用时才实例化)。因此,双重检查单例模式是有懒加载的,线程安全的,效率高的。
/**
* 6.单例模式 双重检查锁
* 线程安全 效率高
*/
public class DoubleCheckSingleton {
// 1.volatile关键字修饰 类的对象(也是私有静态变量)
//保证内存可见性
//保证了不同线程对该变量进行操作的可见性,即一个线程修改了,另一个线程立即可见
//可理解为一个轻量级的synchronized ;语法上volatile只能用于修饰变量
private static volatile DoubleCheckSingleton instance;
// 2.私有化构造函数
private DoubleCheckSingleton() {
//...
}
// 3.第二次判断为同步代码块,配合volatile修饰的变量instance以此实现双重检查
public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
测试编码:
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试6 6.单例模式 双重检查锁
DoubleCheckSingleton doubleCheckSingleton = DoubleCheckSingleton.getInstance();
DoubleCheckSingleton doubleCheckSingleton2 = DoubleCheckSingleton.getInstance();
//判断这两实例 是否为同一个
System.out.println(doubleCheckSingleton == doubleCheckSingleton2);
}
}
测试结果
true
优点:有懒加载的,线程安全的,效率高的(在第2点具体编码中有过代码分析)。
实际开发推荐使用!
前期知识:
充分利用了前期知识的那几个机制:
因为类装载时该类里面的静态内部类不会被装载,所以StaticInnerClassSingleton装载是,它的静态内部类SingletonInstance不会被装载,所以里面的变量不会被实例化,以此达到了实例懒加载的目的。
因为类装载时线程是安全的,这个JVM机制保证了在初始化实力的时候只有一个线程。所以在多线程下,不会出现多个线程同时调用getInstance()方法生成多个实例的情况。
静态变量只在第一次加载类的时候初始化,因此静态内部类的静态变量INSTANCE自始至终只会初始化一次,达到了单例的效果。
/**
* 6.静态内部类
*
* 使用了以下JVM机制:
* 1.类装载时,其静态内部类不会被装载
* 2.类装载时,是线程安全的
* 3.静态变量只在第一次加载类的时候初始化
* (优点:效率高;线程安全;懒加载:调用getInstance时静态内部类装载)
*/
public class StaticInnerClassSingleton {
// 1.私有化构造函数
private StaticInnerClassSingleton() {
}
// 2.静态内部类(含有一个静态变量为StaticInnerClassSingleton类的对象)
private static class SingletonInstance {
private static final StaticInnerClassSingleton INSTANCE
= new StaticInnerClassSingleton();
}
// 3.对外暴露获取实例的静态方法
public static StaticInnerClassSingleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试7 7.静态内部类单例模式
StaticInnerClassSingleton staticInnerClassSingleton = StaticInnerClassSingleton.getInstance();
StaticInnerClassSingleton staticInnerClassSingleton2 = StaticInnerClassSingleton.getInstance();
//判断这两实例 是否为同一个
System.out.println(staticInnerClassSingleton == staticInnerClassSingleton2);
}
}
优点:懒加载,线程安全,效率高(在第2点具体编码中有过代码分析)。
推荐使用!
私有化构造函数
/**
* 8.枚举单例模式
*/
enum EnumSingleton {
INSTANCE;
public void methodDemo(){
//该类的方法demo
System.out.println("这是使用了枚举单例模式的类的demo方法");
}
//其他方法...
}
测试编码:
package DesignPattern.SingletonPattern;
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试8 8.枚举单例模式
EnumSingleton enumSingleton=EnumSingleton.INSTANCE;
EnumSingleton enumSingleton2=EnumSingleton.INSTANCE;
//判断这两实例 是否为同一个
System.out.println(enumSingleton == enumSingleton2);
enumSingleton.methodDemo();//调用demo方法
}
}
测试输出:
true
这是使用了枚举单例模式的类的demo方法
优点:写法极其简单,线程安全,还可防止反序列化来创建新的对象
推荐使用!
Java设计模式-单例模式的7种写法 的总结笔记就到此结束了!
笔记参考的开放视频资源:B站尚硅谷官方—尚硅谷图解Java设计模式韩顺平老师2019力作