//饿汉式单例
public class Hungry {
?
private Hungry(){
?
}
?
private final static Hungry HUNGRY = new Hungry();
?
public static Hungry getInstance(){
return HUNGRY;
}
}
可能浪费内存(饿汉式一开始就将程序中所有的东西都加载好,但有些东西暂时用不到,就会浪费内存)
//懒汉式单例
public class LazyMan {
?
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"完成");
}
?
private volatile static LazyMan lazyMan;
?
// 双重检测锁模式的懒汉式单例(DCL懒汉式)
public static LazyMan getInstance(){
// 加锁(此锁保证只有这个类只有一个)
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
/**
* 此代码会经过的步骤
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* 平时我们期望的运行路线是123,但实际的运行路线可能是132
* A线路没问题,可以正常通过
* 但如果还有一个B线路,因为此对象已经先指向了这个空间
* B线路就会认为该构造对象不为null,但此时lazyMan还没有完成构造,可能会出现问题
*/
//在极端情况下此部分代码是有问题的,因为它不是原子性操作
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
?
// 单线程下确实没问题
// 多线程并发的情况下是有问题的,会同时实例化多个单例
public static void main(String[] args) {
for (int i = 0; i < 10; i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
一般的懒汉模式在单线程下没问题,但没有加synchronized锁的情况下在多线程环境下会出现生成不止一个对象的情况。
懒汉式单例实现思想为需要用到的时候再对程序中的东西进行加载,不需要时不加载,避免了浪费内存
//静态内部类
public class Holder {
private Holder(){}
?
public static Holder getInstance(){
return InnerClass.HOLDER;
}
?
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
//懒汉式单例
public class LazyMan {
?
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"完成");
}
?
private volatile static LazyMan lazyMan;
?
// 双重检测锁模式的懒汉式单例(DCL懒汉式)
public static LazyMan getInstance(){
// 加锁(此锁保证只有这个类只有一个)
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
/**
* 此代码会经过的步骤
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* 平时我们期望的运行路线是123,但实际的运行路线可能是132
* A线路没问题,可以正常通过
* 但如果还有一个B线路,因为此对象已经先指向了这个空间
* B线路就会认为该构造对象不为null,但此时lazyMan还没有完成构造,可能会出现问题
*/
//在极端情况下此部分代码是有问题的,因为它不是原子性操作
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
?
// 在反射面前所有的单例模式都是不安全的
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
// Accessable属性是继承自AccessibleObject 类. 功能是启用或禁用安全检查。它不是让字段的访问权限变了,私有字段依然是私有字段,非本类对象依然是不能直接访问该字段的。它的作用是,不进行访问检查,当执行后面的操作的时候,JVM会直接忽略字段的访问控制符,直接进行操作。
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
?
// 此处获取的结果本该是一样的,因为他们是同一个单例模式,但实际上通过setAccessible绕过访问检查,通过declaredConstructor new了一个新的Instance,创建出了两个都指向单例模式的对象
/**
* 得出结果为:
* LazyMan@16d3586
* LazyMan@154617c
* 得出结论:反射可以破坏单例模式
*/
?
System.out.println(instance);
System.out.println(instance2);
}
}
?
?
private LazyMan(){
// 反射破坏单例的反制手段
synchronized (LazyMan.class){
// 在此处判断类是否为空,为空正常创建,如果有值,则判定第二次创建是使用反射来破坏单例模式的
if(lazyMan!=null){
throw new RuntimeException("拦截反射破坏单例");
}
}
System.out.println(Thread.currentThread().getName()+"完成");
}
因为反射走了单例模式的无参构造器,所以可以在构造器中加把锁进行判断
public static void main(String[] args) throws Exception {
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
?
System.out.println(instance);
System.out.println(instance2);
生产者生产,消费者等待,生产完成后通知消费。
消费者消费,生产者等待,消费者消费完成后通知生产
private static boolean wenying = false;
private LazyMan(){
if(wenying == false){
wenying = true;
}else {
throw new RuntimeException("拦截反射破坏单例");
}
}
}
public static void main(String[] args) throws Exception {
?
// 假设知道标志位的name,在此处获取标志位的值
Field wenying = LazyMan.class.getDeclaredField("wenying");
// 再次通过setAccessible绕过访问检查
wenying.setAccessible(true);
?
?
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
LazyMan instance = declaredConstructor.newInstance();
// 更改标志位的值
wenying.set(instance,false);
?
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
根据查看源码得知,如果反射传过来的是一个枚举常量,则会提示不能使用反射破坏枚举
通过target查看EnumSingle源码为
public enum EnumSingle { INSTANCE; private EnumSingle() { } public EnumSingle getInstance() { return INSTANCE; } }
那么此时可以使用之前用过的,反射使用类的构造方法来破坏枚举:
//enum是一个什么? 本身也是一个class类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
运行结果为:Exception in thread "main" java.lang.NoSuchMethodException: EnumSingle.<init>(),并不是源码中的Cannot reflectively create enum objects。翻译为EnumSingle中没有无参构造。
但是在target中这个类确实是有无参构造的,进入该类的文件夹地址,使用cmd通过javap进行反编译,能得到枚举类其实就是java类继承了枚举的结论,但此时编译出来,依然有无参构造方法,实际运行时报错明明已经说明了此类没有无参构造。
使用jad反编译工具再次进行编译,将该类转换为java类,此时原本类中的无参构造变为了构造方法(String s,int i)的方法
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://kpdus.tripod.com/jad.html // Decompiler options: packimports(3) // Source File Name: EnumSingle.java public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }
再次回到程序中进行试验
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
最终运行结果为Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects,运行正确
原文:https://www.cnblogs.com/yunchuran/p/14497828.html