单例模式

单例模式

​ 单例模式是一种常见的设计模式,它保证了一个类只有一个唯一的对象,提供一个全局的访问点,例如,我们只需要用一个对象去管理配置文件,这时候就可以用单例模式。

单例模式的分类

饿汉式单例

1.静态常量法

//饿汉单例,线程安全
public class Hungry {

    //构造器私有
    private Hungry(){

    }

    private static final  Hungry hungry = new Hungry();

    //如果没被使用,这种情况会存在内存浪费
    private  int[] arr = new int[1024];

    public static Hungry getInstance(){
        return  hungry;
    }

}

2.静态代码块

public class HungryBlock {

    private static HungryBlock hungryBlock;

    private HungryBlock() {

    }

    static {
        hungryBlock = new HungryBlock();
    }
   
    public static HungryBlock getInstance() {
        return hungryBlock;
    }
}

3.静态内部类

public class HungryInner {
    private HungryInner() {

    }
    public static class HungryInner2{
        private static final HungryInner hungryInner = new HungryInner();
    }
    public static HungryInner getInstance() {
        return HungryInner2.hungryInner;
    }

}

懒汉式单例

​ 等到真正使用的时候才去创建实例,不用时不去主动创建。

1.判断一次且不加锁,存在线程安全问题

//懒加载,会存在线程安全问题
public class lazyInstance {

    private static lazyInstance obj = null;
    private lazyInstance() {
        
    }
    public static lazyInstance getInstance() {
        if(obj == null) {
            obj = new lazyInstance();
        }
        return obj;
    }
}

2.对getInstance加上synchronized关键字,线程安全,每次获取对象时都会加锁,性能低下

public class lazyInstance {

    private static lazyInstance obj = null;
    private lazyInstance() {
        
    }
    public static lazyInstance getInstance() {
        if(obj == null) {
            obj = new lazyInstance();
        }
        return obj;
    }
}

3.用synchronized对类加同步锁+两次检查,也会存在线程安全问题,因为 obj = new lazyInstance();不具有原子性,正常情况下是先分配内存空间,再初始化对象,最后obj指向对象的内存空间,但最后两步可能存在指令重排序,导致obj先指向内存空间,从而使得其他线程获取到空对象。

public class lazyInstance {

    private static lazyInstance obj = null;
    private lazyInstance() {
       
    }
    public static lazyInstance getInstance() {
        if(obj == null) {
            synchronized(lazyInstance.class) {
                if(obj == null) {
                    obj = new lazyInstance();
                }
            }
        }
        return obj;
    }

}

4.双重校验+volatile,这种写法可以实现单例,但是在特殊情况下也会存在问题,因为反射可以无视类的私有构造方法,创造出新对象。

public class lazyInstance {

    private static volatile lazyInstance obj = null;
    private lazyInstance() {

    }
    public static lazyInstance getInstance() {
        if(obj == null) {
            synchronized(lazyInstance.class) {
                if(obj == null) {
                    obj = new lazyInstance();
                }
            }
        }
        return obj;
    }
}

例如

public class LazyInstance {

    private static volatile LazyInstance obj;
    private LazyInstance() {
           synchronized (LazyInstance.class){
               if(obj!=null){
                   throw  new  RuntimeException("对象已存在");
               }
           }
    }
    public static LazyInstance getInstance() {
        if(obj == null) {
            synchronized(LazyInstance.class) {
                if(obj == null) {
                    obj = new LazyInstance();
                }
            }
        }
        return obj;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyInstance instance = LazyInstance.getInstance();
        //获取空参构造器哦
        Constructor<LazyInstance> constructor = LazyInstance.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyInstance instance2 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);

    }

}

利用反射可以生成一个可以和getInstance()获取的不一样的instance2对象,当然也可以解决,在构造器里面加上校验即可,上面代码已经给出。

不加校验结果:

image-20201017172534026

加校验结果:

image-20201017173104352

5.枚举创建单例(最完美)

public enum EnumSingle {


    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
posted @ 2020-10-27 14:49  夏雨初晴  阅读(50)  评论(0)    收藏  举报