深入了解单例模式
一天小C在路上走 突然看见他的好朋友小A和小B 小C正准备和小A,小B打招呼。突然看见小A和小B正在争论什么,小C有些好奇,小C刚上去就听见小A在说单例模式中的线程安全问题。小C心想:“诶,刚好我昨天刚学习了单例模式,这下可以给他们吹一下牛了”。“单例模式中有懒汉式和饿汉式,懒汉式分为线程安全和线程不安全,饿汉式是线程安全的,对吧”,小C说到。小A和小B回头看着小C,这时小A说到:“那你知道懒汉式和饿汉式的安全问题吗?”,“这还不简单吗,懒汉式的可以在方法上添加锁来保证安全,饿汉本来就是线程安全的对吧",小C说到。”不错,但单例模式不仅仅只有这些哦",小A说到。
从线程安全开始
DCL懒汉式
在判断方法中加入双重锁判断
public class Lazy{
private volatile static Lazy lazy;
//私有化构造方法
private Lazy(){}
public static Lazy getLazy(){
//如果lazy为空 说明是第一次创建
if(lazy==null){
//加锁 Lazy.class是唯一的
synchronized (Lazy.class){
//在这里判断是避免高并发的时候 多个请求通过了第一个判断
//因为上面有一把锁 把别的请求拦在外面 只有一个请求进来
//当这个请求创建了对象了 那么后面的请求就不需要在创建了 就直接在这里把后面的请求给拦截下来
if(lazy==null){
//创建对象
lazy = new Lazy();
}
}
}
//返回对象
return lazy;
}
}
因为创建对象不是一个原子性操作:1.分配内存空间,2.创建对象,并且初始化对象,3.对象指向内存空间
这是可能会出现一个指令重排的问题 原本应该执行1,2,3 但可能会出现执行1,3,2 为了避免出现这种情况我们需要给
private static Lazy lazy; 加上volatile关键字 就可以避免指令重排的问题
private volatile static Lazy lazy;
volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
静态内部类
第三种创建单例模式的方法
public class Holder{
//单例模式的构造方法都是私有化的
private Holder(){}
public static Holder getInstace(){
return InnerClass.HOLDER;
}
public static class InnerClass{
//final
private static final Holder HOLDER = new Holder;
}
}
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程。
问答(单例模式的深入了解):
小B: 这样也不安全 我可以通过反射来创建
//反射
public static void main(String[] args) throws Exception {
Lazy lazy = Lazy.getLazy();
// 获取构造方法
// public Constructor[] getConstructors():所有公共构造方法
// public Constructor[] getDeclaredConstructors():所有构造方法
//返回Lazy的空参构造器
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//修改类中无参构造器的访问权限
declaredConstructor.setAccessible(true);
//newInstance创建对象
//newInstance创建类是这个类必须已经加载过且已经连接
//newInstance: 弱类型(GC是回收对象的限制条件很低,容易被回收)、低效率、只能调用无参构造
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy);
System.out.println(lazy1);
}
//测试结果
demo.Lazy@1540e19d
demo.Lazy@677327b6
小A:“既然你用无参构造来创建 那么我在里面进行判断 就行了 ”
//私有化构造方法
private Lazy(){
//上锁
synchronized (Lazy.class){
//判断
if(lazy!=null){
throw new RuntimeException("不要想通过反射来破坏结构");
}
}
}
//测试
Caused by: java.lang.RuntimeException: 不要想通过反射来破坏结构
at demo.Lazy.<init>(Lazy.java:13)
... 5 more
小B:那我就不创建对象 注解通过反射来创建对象
//反射
public static void main(String[] args) throws Exception {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
//测试
demo.Lazy@677327b6
demo.Lazy@14ae5a5
小A:那我就加一个添加一个变量 只要你不知道这个变量名称 那么你也无法创建
private volatile static Lazy lazy;
private static boolean panduan = false;
//私有化构造方法
private Lazy(){
synchronized (Lazy.class){
if(panduan==false){
panduan = true;
}else {
throw new RuntimeException("不要想通过反射来破坏结构");
}
}
}
//测试
Caused by: java.lang.RuntimeException: 不要想通过反射来破坏结构
at demo.Lazy.<init>(Lazy.java:16)
... 5 more
小B:我可以通过反编译来找到你的变量,再通过修改变量的值继续创建对象
//反射
public static void main(String[] args) throws Exception {
//获取到变量
Field panduan = Lazy.class.getDeclaredField("panduan");
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//修改类中无参构造器的访问权限
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
//修改上一个对象的变量值为false
panduan.set(lazy1,false);
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
//测试
demo.Lazy@677327b6
demo.Lazy@14ae5a5
小A :那我就使用枚举类型
public enum Enum {
INSTANCE;
public Enum getInstance(){
return INSTANCE;
}
}
小B:我就不信了,我还真就要试试了
public static void main(String[] args) throws Exception {
Constructor<Enum> declaredConstructor = Enum.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Enum anEnum = declaredConstructor.newInstance();
Enum anEnum2 = declaredConstructor.newInstance();
//demo.Enum.<init>() 没有空参的构造器
System.out.println(anEnum);
System.out.println(anEnum2);
}
//测试
Exception in thread "main" java.lang.NoSuchMethodException: demo.Enum.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at demo.test.main(Enum.java:13)
小B:诶? 为什么是没有空参构造器这个异常? 难道不是抛throw new IllegalArgumentException(“Cannot reflectively create enum objects”);异常吗? 我反编译看看 class文件中有没有空参构造方法
E:\collection\HashSet\target\classes\demo>javap -p Enum.class
Compiled from "Enum.java"
public final class demo.Enum extends java.lang.Enum<demo.Enum> {
public static final demo.Enum INSTANCE;
private static final demo.Enum[] $VALUES;
public static demo.Enum[] values();
public static demo.Enum valueOf(java.lang.String);
#这里存在
private demo.Enum();
static {};
诶? 这class文件中有啊 ,不行我要直接把文件反编译为java文件看看
通过反编译 发现没有无参构造 有一个有参构造
public static void main(String[] args) throws Exception {
Constructor<Enum> declaredConstructor = Enum.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
Enum anEnum = declaredConstructor.newInstance();
Enum anEnum2 = declaredConstructor.newInstance();
//demo.Enum.<init>() 没有空参的构造器
System.out.println(anEnum);
System.out.println(anEnum2);
}
//测试
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at demo.test.main(Enum.java:19)
现在这个异常就是 Cannot reflectively create enum objects 不能反射地创建枚举对象
好吧枚举确实不能被反射破坏