单例模式,反射破环?
饿汉式
// 饿汉式单例
public class Hungry {
//构造器私有
private Hungry(){
}
// 一上来就把这个类加载了
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
// 饿汉式单例
public class Hungry {
// 这4组数据非常耗内存资源,饿汉式一上来就把所有的内存里面的东西全部加载进来了,就存在这个空间
// 但这个空间现在是没有使用的,可能会造成浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//构造器私有
private Hungry(){
}
// 一上来就把这个类加载了
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
饿汉式单例可能会造成浪费空间,所以想要用的时候再去创建这个对象,平时就先放在这个地方,于是就出现了懒汉式!
懒汉式
// 懒汉式单例
public class LazyMan {
// 构造器私有
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
它是有问题的,单线程下确实单例ok,多线程并发就会出现问题!
测试
// 懒汉式单例
public class LazyMan {
// 构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
发现单例有问题,每次结果可能都不一样!
解决
// 懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
private static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
但在极端情况下还是可能出现问题
经历三个步骤:
1、 分配内存空间
2、 执行构造方法,初始化对象
3、 把这个对象指向这个空间
有可能会发生指令重排的操作!
比如,期望它执行 123 ,但是它真实可能执行132,比如第一个A线程过来执行了132,先分配空间再吧这个空间占用了,占用之后再去执行构造方法,如果现在突然来了个B线程,由于A已经指向这个空间了,它会以为这个 lazyMan 不等于 null ,直接return ,此时lazyMan还没有完成构造,所以必须避免这个问题!
必须加上volatile
// 懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
// 避免指令重排
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
// 静态内部类
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()+":: ok");
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 无视了私有的构造器
// 通过反射创建对象
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
结论:反射可以破坏这种单例
解决
// 懒汉式单例
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图使用反射破环 异常");
}
}
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 无视了私有的构造器
// 通过反射创建对象
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
但是如果都用反射创建对象的情况下,还是会破环单例!
测试
解决
// 懒汉式单例
public class LazyMan {
// 标志位
private static boolean abc = false;
private LazyMan(){
synchronized (LazyMan.class){
if (abc==false){
abc=true;
}else {
throw new RuntimeException("不要试图使用反射破环 异常");
}
}
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 无视了私有的构造器
// 通过反射创建对象
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
但是如果被人知道 abc
这个变量,也可以破环!
单例又被破环了!
看一下源码
它说不能使用反射破环枚举,枚举是jdk1.5出现的,自带单例模式!
测试,写一个枚举类
// enum 本身就是一个class类
public enum EnumSingle {
INSTANCE;
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 Exception {
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);
}
}
它竟然说我现在的这个枚举类中没有空参构造器!
然后就去源码里分析!
找到这个class文件!利用javap反编译一下!
发现这个也显示有一个空参构造,证明这个也不对,用第三方的工具查看!
利用它再吧class文件生成java文件!
打开这个java文件
证明是idea和源码骗了我!
再次尝试破环!
// enum 本身就是一个class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
结论:反射无法破环枚举类!