**
单例模式
一个类就一个实例,自己new这个实例对象(不允许别人new),并为该实例提供一个全局访问点(写静态方法,通过类名.静态方法名去调),核心是无参构造方法私有。
1.饿汉单例
//饿汉模式单例
public class Hungry {
//私有的无参构造,别人就不能通过new来获得单例对象 只能用Hungry.getInstance
private Hungry() {
}
private static Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
饿汉单例相比于懒汉,他是先new实例的,所以饿汉调实例速度快,并且他没有懒汉那样的线程安全问题,缺点就是比较耗费资源,比如我只是访问了类中的一个静态变量,就会使得这个类去new实例出来(即使用不到它)。
2.懒汉单例
public class Lazy {
//加一个红绿灯 防止多次反射创建单例
private static boolean flag=false;//但是这个flag也会可以通过反射改值
private Lazy(){
//无参构造私有防不了反射
//解决方法 抛异常
synchronized (Lazy.class){
if(flag==false){
flag=true;
}else {
throw new RuntimeException("反射创建单例异常");
}
}
};
private static volatile Lazy lazy;
//双端检测,保证单例
public static Lazy getInstance(){
if(lazy==null){
synchronized (Lazy.class){
if(lazy==null){
lazy=new Lazy();//不是一个原子性操作
/*
1.给对象在堆上分配内存空间
2.执行构造方法,初始化对象
3.引用指向刚刚分配好的空间
我们希望123
但是实际可能是132
当线程A执行到了13时候,线程B进来,发现lazy!=null,就直接return没有初始化的lazy了
空指针异常就会出现
故这里还要加上volatile禁止指令重排
*/
}
}
}
return lazy;
}
//反射破坏单例,因为反射可以进入私有的构造器创建对象
public static void main(String[] args) throws Exception {
//正常方法拿单例
Lazy instance1 = Lazy.getInstance();
Lazy instance2 = Lazy.getInstance();
//反射的方法拿单例,强行走了私有的构造方法
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Lazy instance3 = declaredConstructor.newInstance();
System.out.println(instance1==instance2);//true
System.out.println(instance1==instance3);//false
}
}
懒汉单例问题就多了,先谈一下它的好处,懒加载,用到的时候再new,比较省资源。
为了避免多线程创建多个不同的单例对象,要双端检测加sychronized,另外由于new对象它不是一个原子性操作,为了防止报空指针异常,还需要加volatile关键字去禁止指令重排。最骚的是,通过反射可以无视掉你的私有方法去获得单例,我们这里可以加一个flag(红绿灯),去防止反射多次创建单例对象,但是道高一丈魔高一尺,就连那个flag,我也可以通过反射的方法给你改值。所以这里就引入了枚举中的单例,在newInstance这个方法的源码里,有这么一句
throw new IllegalArgumentException("Cannot reflectively create enum objects");
就是说枚举的单例是禁止通过反射的方法获取实例对象的。
//枚举可以把常量包装成对象
//通过枚举创建的单例不能通过反射调用
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();
//IllegalArgumentException: Cannot reflectively create enum objects
System.out.println(instance1 == instance2);
}
}
结果是
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
3.内部类写单例
//静态内部类
public class Holder {
private Holder(){};
public static Holder getInstance(){
return InnerClass.holder;
}
//静态内部类,懒加载,用到才会加载这个静态内部类
static class InnerClass{
private static Holder holder=new Holder();
}
public static void main(String[] args) {
Holder holder=Holder.getInstance();
}
}
在内部类中去new,有懒汉的优点,用到才会去加载这个内部类。但缺点就是还是没有饿汉快(毕竟他饿么,他早就端好碗了)