饿汉式单例
饿汉式单例设计模式,就是将类的静态实例作为该类的一个成员变量,也就是说在 JVM 加载它的时候就已经创建了该类的实例,因此它不会存在多线程的安全问题
package com.study.single;
//饿汉式单例
public class Hungry {
//造成空间资源的浪费
private Hungry(){}
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
实例虽然作为该类的一个实例变量,但是他不主动进 行创建,如果你不使用它那么他将会永远不被创建,只有你在第一次使用它的时候才会被创建,并且得到保持
package com.study.single;
//懒汉式单例
public class LazyMan {
//构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"启动");
}
private static LazyMan lazyMan=null;
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();
}
}
}
DCL懒汉式
多线程的情况下,如果线程A和线程B同时判断了lazeMan为null,他们都实例化了,那么就变得很危险了,所以我们加上synchronized锁
public static LazyMan getInstance(){
//双重检测锁模式的懒汉式 简称DCL懒汉式
if (lazyMan==null){
synchronized(LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
在lazeMan=new LazeMan这一步时,它不是一个原子性操作,它进行了三步:
第一步:分配内存空间
第二步:执行构造方法,初始化对象
第三步:把这个对象指向这个空间
我们期望他按照123来执行,但他可能是按照132来执行的,那么如果A线程按照132执行完毕,此时B线程来了,由于已经指向了这个空间,他就会认为lazyMan!=null,直接return lazyMan,而这个时候lazeMan还没有完成构造
所以我们需要volatile关键字禁止指令重排优化
private volatile static LazyMan lazyMan=null;
public static LazyMan getInstance(){
//双重检测锁模式的懒汉式 简称DCL懒汉式
if (lazyMan==null){
synchronized(LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
完善懒汉式单例
通过反射可以破坏单例模式
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
LazyMan lazyMan1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor =
LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1.hashCode()==lazyMan2.hashCode());
}
解决:在无参构造器中进行一次判断
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图通过反射来破坏!");
}
}
}
那当我两个对象都用反射,发现还是被破坏了
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// LazyMan lazyMan1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor =
LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1.hashCode()==lazyMan2.hashCode());
}
解决方法:定义一个标识符
变量名随便定义,初始值为false,那么它第一次执行完,无论通不通过反射,他都会变成true,下一次就会抛出异常,变量名还可以做一些加密,让反射变得更安全
private static boolean safasfsdfasd=false;
//构造器私有
private LazyMan(){
synchronized (LazyMan.class){
if (safasfsdfasd==false){
safasfsdfasd=true;
}else {
throw new RuntimeException("不要试图通过反射来破坏!");
}
}
}
那么假设这个变量名被知道了,就又可以进行反射破坏
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// LazyMan lazyMan1=LazyMan.getInstance();
Field safasfsdfasd1 = LazyMan.class.getDeclaredField("safasfsdfasd");//获取字段
safasfsdfasd1.setAccessible(true);//破坏私有权限
Constructor<LazyMan> declaredConstructor =
LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
safasfsdfasd1.set(lazyMan1,false);//把第一个对象的值再改为false
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1.hashCode()==lazyMan2.hashCode());
}
使用枚举来解决
枚举
package com.study.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum Enum {
INSTANCE;
public Enum getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Enum instance1 = Enum.INSTANCE;
Constructor<Enum> enumConstructor =
Enum.class.getDeclaredConstructor(null);
enumConstructor.setAccessible(true);
Enum instance2 = enumConstructor.newInstance();
System.out.println(instance1.hashCode()==instance2.hashCode());
}
}
我们的报错
正确的报错
发现它报错 说没有这个空的构造器,但我们看它的class文件
发现他是有这个空的构造器的 但我们实际执行起来是没有这个空参构造的 它是骗我们的
那么我们进行反编译 它也有一个空参构造 说明它还是骗了我们
使出我的必杀技 使用更高级的反编译工具 jdp.exe
Enum.java
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Enum.java
package com.study.single;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public final class Enum extends Enum
{
public static Enum[] values()
{
return (Enum[])$VALUES.clone();
}
public static Enum valueOf(String name)
{
return (Enum)Enum.valueOf(com/study/single/Enum, name);
}
private Enum(String s, int i)
{
super(s, i);
}
public Enum getInstance()
{
return INSTANCE;
}
public static void main(String args[])
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException
{
Enum instance1 = INSTANCE;
Constructor enumConstructor = com/study/single/Enum.getDeclaredConstructor(null);
enumConstructor.setAccessible(true);
Enum instance2 = (Enum)enumConstructor.newInstance(new Object[0]);
System.out.println(instance1.hashCode() == instance2.hashCode());
}
public static final Enum INSTANCE;
private static final Enum $VALUES[];
static
{
INSTANCE = new Enum("INSTANCE", 0);
$VALUES = (new Enum[] {
INSTANCE
});
}
}
可以看见他只有一个有参构造 String和Int
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Enum instance1 = Enum.INSTANCE;
Constructor<Enum> enumConstructor =
Enum.class.getDeclaredConstructor(String.class,int.class);
enumConstructor.setAccessible(true);
Enum instance2 = enumConstructor.newInstance();
System.out.println(instance1.hashCode()==instance2.hashCode());
}
}
得出正确的结果
反射无法破坏枚举的单例
jad下载地址:JAD Java Decompiler Download Mirror (varaneckas.com)