标签:方法 false mamicode 第三方 ESS 这一 全局 默认 hold
public class Hungry {
//浪费空间
private byte[] data1 = new byte[1024*1024];
private Hungry(){
}
private static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
饿汉式在类被初始化时就已经在内存中创建了对象,以空间换时间,故不存在线程安全问题。
public class Lazy {
private static Lazy lazy;//默认为null
private Lazy() {
}
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
懒汉式在方法被调用后才创建对象,以时间换空间,在多线程环境下存在风险。
public class Lazy {
private Lazy() {
}
private static Lazy lazy;
//DCL懒汉式(双重检测锁)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
DCL模式的优点就是,只有在对象需要被使用时才创建。
第一次判断 INSTANCE == null避免非必要加锁的情况,这样可以提高执行效率。
使用synchronized锁住实例对象是保证程序在多线程环境下的安全性。
由于JVM存在乱序执行功能(指令重排),DCL也会出现在多线程场景下不安全的情况。例如:Lazy lazy = new Lazy();
这一段代码不是原子性操作,在JVM里分为三步:
解决办法: private volatile static Lazy lazy;
加一个volatile关键字可以避免指令重排。
public class SingleTon{
private SingleTon(){}
private static class SingleTonHolder {
private final static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHolder.INSTANCE;
}
}
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不浪费内存。
即当SingleTon第一次被加载时,并不需要去加载SingleTonHolder,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE.
第一次调用getInstance()方法会使虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
静态内部类单例模式的核心原理为对于一个类,JVM在仅用一个类加载器加载它时,静态变量的赋值在全局只会执行一次。
静态内部类也有缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部不方便传递参数进去。
这里会用代码来展示反射是如何破坏DCL饿汉模式的。
第一场对决
DCL方:
public class Lazy {
private Lazy() {
}
private volatile static Lazy lazy;//volatile避免指令重排
//DCL懒汉式(双重检测锁)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();//不是原子性操作
}
}
}
return lazy;
}
}
反射方:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Lazy lazy1 = Lazy.getInstance();
//反射开始操作
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);//获取构造器
declaredConstructor.setAccessible(true);//无视私有构造器
Lazy lazy2 = declaredConstructor.newInstance();//通过构造器创建另一个实例
System.out.println(lazy1.hashCode());//输出实例对象的哈希值
System.out.println(lazy2.hashCode());
}
结果:出现了两个不同的实例对象,反射赢。
460141958
1163157884
第二场对决
DCL方:
public class Lazy {
//在构造器加了锁和条件判断
private Lazy() {
synchronized (Lazy.class){
if(lazy != null){
throw new RuntimeException("不要试图使用反射破坏单例模式!");
}
}
}
private volatile static Lazy lazy;//volatile确保原子性,避免指令重排
//DCL懒汉式(双重检测锁)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();//不是原子性操作
}
}
}
return lazy;
}
反射方:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
//两次都使用反射创建对象
Lazy lazy1 = declaredConstructor.newInstance();
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1.hashCode());
System.out.println(lazy2.hashCode());
}
}
结果:出现了两个不同的实例对象,反射赢。
460141958
1163157884
第三场对决
DCL方:
public class Lazy {
//加入标志位
private static boolean flag = false;
private Lazy() {
//进行标志位判断
if(flag == false){
flag = true;
}
else {
throw new RuntimeException("不要试图使用反射破坏单例模式!");
}
}
private volatile static Lazy lazy;//volatile确保原子性,避免指令重排
//DCL懒汉式(双重检测锁)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();//不是原子性操作
}
}
}
return lazy;
}
反射方:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
Lazy lazy1 = declaredConstructor.newInstance();
Field flag = Lazy.class.getDeclaredField("flag");//通过反射获取标志位字段
flag.setAccessible(true);//破坏字段私有性
flag.set(lazy1,false);//再将标志位改回false
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1.hashCode());
System.out.println(lazy2.hashCode());
}
结果:出现了两个不同的实例对象,反射赢。
1163157884
1956725890
综上,反射完胜!
枚举方:
//枚举本身也是一个类
public enum Enum {
INSTANCE;
public Enum getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
Enum instance1 = Enum.INSTANCE;
Enum instance2 = Enum.INSTANCE;
//经检测,instance1和instance2是同一个对象。
System.out.println(instance1);
System.out.println(instance2);
}
}
以上就是使用枚举的方式实现单例模式,它的确可以保证一个类只有一个实例对象。我们现在唯一担心的就是反射。反射那么厉害,它可以破坏这种单例模式吗?让我们来试一下。
首选,我们先找到这个文件编译过后的字节码文件Enum.class
。
然后利用反编译工具将此字节码文件反编译成java文件。
使用Idea自带的反编译工具。
使用命令行javap -p Enum.class
。
使用第三方反编译工具jad的命令jad -sjava Enum.class
。
jad反编译后的文件最准确,所以Enum类的构造器是private Enum(String s, int i){super(s,i);}
。
反射方:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Enum> declaredConstructor = Enum.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
Enum enum1 = declaredConstructor.newInstance();
System.out.println(enum1.hashCode());
Enum enum2 = declaredConstructor.newInstance();
System.out.println(enum2.hashCode());
}
结果:报错了,无法通过反射的方法破坏单例模式,枚举胜!
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
标签:方法 false mamicode 第三方 ESS 这一 全局 默认 hold
原文地址:https://www.cnblogs.com/baihan/p/13020749.html