根据狂神说写的笔记:https://www.bilibili.com/video/BV1K54y197iS?from=search&seid=2247731384023907916
一、饿汉式单例(程序一开始就加载)
package com.yyh.danlimoshi;
//饿汉模式,一开始就全部加载
public class HungryMan {
int[] a=new int[4];
int[] a1=new int[4];
//构造器私有
private HungryMan(){
}
private static HungryMan HUNGRY_MAN =new HungryMan();
public HungryMan getInstance(){
return HUNGRY_MAN;
}
}
二、懒汉式单例(用才加载,不用不加载)
package com.yyh.danlimoshi;
//懒汉式 用到的时候才加载
public class LazyMan {
//构造器私有
private LazyMan(){
System.out.println("线程启动"+Thread.currentThread().getName());
}
private static LazyMan LAZY_MAN=null;
public static LazyMan getIntance(){
if(LAZY_MAN==null){
LAZY_MAN=new LazyMan();
}
return LAZY_MAN;
}
public static void main(String[] args) {
for (int i = 0; i <9 ; i++) {
new Thread(()->{
LazyMan.getIntance();
}).start();
}
}
}
这个程序单线程模式没问题,多线程出现下面问题
线程只启动了两个,成功的次数每次不一样
解决:加双层锁,锁class,每次进去的线程只有一个
private static LazyMan LAZY_MAN=null;
public static LazyMan getIntance(){
//加两层锁是为了提高效率,线程进入第一个if语句,假如LazyMan类锁被其他线程占用,则等待,这个等待可以理解为在锁内等待,所以提高效率
if(LAZY_MAN==null){
synchronized (LazyMan.class){
if(LAZY_MAN==null){
LAZY_MAN=new LazyMan();
}
}
}
return LAZY_MAN;
}
//DCL懒汉式
以上代码还是会出现问题,因为 LAZY_MAN=new LazyMan()不是原子操作,new的过程中分三步:
1.分配内存空间
2.执行构造方法,初始化对象
3.把初始化的对象指向分配的空间
这个过程可能发生指令重排,比如:
A线程执行过程为132的时候,执行没问题
然后线程B执行的时候因为已经指向了分配的空间,所以判读if(LAZY_MAN==null)不成立,返回空的LAZY_MAN(空指针异常)
解释:
2初始化对象是一个内存操作,这可能是一个耗时操作,为了避免CPU在等待这个操作时停顿导致性能下降,编译器会调整指令顺序(开启优化)或者有些架构的CPU会乱序执行指令,也就是将3提前执行,这样的话,指针就指向了一个未初始化好的对象。外部得到的单例对象是一个未初始化好的对象,就会引发问题,并不是说new对象的时候出错。这只在多线程场景下才会有小概率出现。指令重排和CPU架构、编译器优化有关,和编程语言无关,不同编程语言都提供了一定的API或赋予关键字一定语义来保证CPU不乱序执行或者编译器不执行指令重排优化。
在C/C++中,volatile是为了提醒编译器在执行的单一线程内, volatile 访问不能被优化掉,但是volatile并不能保证数据是多线程安全的。其他语言也应当是一样的,但是不同语言可能赋予了volatile更多的语义
解决办法:加上volatile防止指令重排
private volatile static LazyMan LAZY_MAN=null;
三、静态内部类单例
package com.yyh.danlimoshi;
//静态内部类
public class Holder {
private Holder(){}
public Holder getInstance(){
return Inner.HOLDER;
}
public static class Inner{
public static Holder HOLDER=new Holder();
}
}
四、以上三个都是不安全的,因为有反射机制,可以全部破解,以懒汉式单例为例(不知道反射的去看反射篇:https://blog.csdn.net/weixin_45653314/article/details/110173952)
/*利用反射机制破解*/
LazyMan intance = LazyMan.getIntance();
//利用反射获取一个对象
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance1.equals(intance));
输出结果为:
解决办法:在私有构造器里面加锁
private LazyMan(){
synchronized (LazyMan.class){
if (LAZY_MAN!=null){
throw new RuntimeException("不要用反射破环单例");
}
}
System.out.println("线程启动"+Thread.currentThread().getName());
}
输出结果:
但是还是有问题,假如把代码换成下面的话,还是可以破坏单例:
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance1.equals(instance));
输出结果:
上面这种情况的话可以通过红绿灯解决(打标记),解决办法:
private static boolean red=false;
//构造器私有
private LazyMan(){
synchronized (LazyMan.class){
if(red==false){
red=true;
}else {
throw new RuntimeException("不要用反射破环单例");
}
}
System.out.println("线程启动"+Thread.currentThread().getName());
}
输出结果:
即使这样也可以利用反射的机制破坏单例:
Field red = LazyMan.class.getDeclaredField("red");
red.setAccessible(true);
//利用反射获取一个对象
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
red.set(instance1,false);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance1.equals(instance));
输出结果:
四、枚举单例
package com.yyh.danlimoshi;
public enum EnumSigle {
RED;
public EnumSigle getInstance(){
return RED;
}
public static void main(String[] args) {
EnumSigle red = EnumSigle.RED;
EnumSigle red1 = EnumSigle.RED;
System.out.println(red==red1);
}
}
现在尝试用反射机制破坏枚举:
package com.yyh.danlimoshi;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSigle {
RED;
public EnumSigle getInstance(){
return RED;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSigle red = EnumSigle.RED;
Constructor<EnumSigle> declaredConstructor = EnumSigle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSigle enumSigle = declaredConstructor.newInstance();
System.out.println(red);
System.out.println(enumSigle);
}
}
运行结果: