单例,单例!

单例模式

单例模式的写法

  • 饿汉式
  • 懒汉模式(延时加载)
  • 双重检查锁
  • 枚举
  • 内部类

饿汉式

饿汉式是绝对线程安全的,但是会导致一个问题,不管用不用都会创建对象,多个无用的单例对象,造成不必要的内存浪费,拖慢系统的启动过程

// 饿汉式两种写法
class Hungry {
    private static final Hungry instance = new Hungry();

    private Hungry() {
    }
}

class Hungry2 {
    private static final Hungry2 instance;

    static {
        instance = new Hungry2();
    }

    private Hungry2() {
    }
}

懒汉式

为了解决饿汉式内存浪费的问题,引入了懒汉模式,当需要使用的时候再初始化.

// 懒汉模式
class Lazy {
    private static Lazy lazy=null;
    private Lazy(){}
    public static Lazy getInstance(){
        if(lazy==null){
            lazy=new Lazy();
        }
        return lazy;
    }
}

但是上面的写法有线程安全的问题:在多线程的情况下,如果多个线程先后进入getInstance()方法,都发现lazy==null,于是都去new对象了,就会导致两次对象不一致,虽然有时候可能是一样的

为了解决懒汉式的线程安全问题,可以给getInstance()加synchronized关键字

/**
 * 懒汉模式加synchronized关键字
 */
class Lazy {
    private static Lazy lazy=null;
    private Lazy(){}
    public synchronized static Lazy getInstance(){
        if(lazy==null){
            lazy=new Lazy();
        }
        return lazy;
    }
}

这时候,就算多线程,每次也只能有一个线程进入方法内部,其他线程只能在方法外等待(MONITOR),等待获得锁的线程执行完才能进入,因为之前的线程已经初始化好了,所以已经实现了线程安全

但是又引来一个新的问题,synchronized锁每一个时刻都只能有一个线程能访问getInstance(),其他线程只能等待,大大降低了CPU的利用率

于是又有了新的改进

/**
 * 懒汉模式加synchronized关键字
 * 改进,让多个线程都能进入方法内部
 */
class Lazy {
    private static Lazy lazy=null;
    private Lazy(){}
    public static Lazy getInstance(){
        synchronized(Lazy.class){
            if(lazy==null){
                lazy=new Lazy();
            }
        }
        return lazy;
    }
}

但是这样还是和之前的一样,虽然多个线程能进来了,CPU稍微能提高了那么一丢丢,但是效果还是差不多

双重检查锁

针对之前的问题,我们可以这样改进

/**
 * 懒汉模式加
 * 双重检查锁
 */
class Lazy {
    private static Lazy lazy = null;

    private Lazy() {
    }

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

双重检查锁在lazynull的时候才进行锁住,但是这样还是有一个问题,如果两个线程都进入,都发现lazynull,一个线程锁住了,然后开始new Lazy(),看似没什么问题,但是在new Lazy()的过程中是会存在一点点问题:CPU指令重排

new一个对象,需要分配内存空间,引用指向内存空间,new 实例,很有可能引用非空,但是实例还没有new出来,这样会导致一个问题,当另一个线程进来,发现引用非空,直接使用了,导致空指针异常

所以还需要对可见性进行控制

/**
 * 懒汉模式加
 * 双重检查锁
 */
class Lazy {
    private static volatile Lazy lazy = null;

    private Lazy() {
    }

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

volatile关键字保证了线程间共享变量的可见性,至此,懒汉模式终于成功的实现了线程安全单例.但是貌似不够优雅啊

静态内部类

/**
 * 静态内部类实现单例,绝对的线程安全
 * 只有外部类被使用,静态内部类才会被加载,也是懒加载
 */
class InnerClassSinglton {
    private InnerClassSingleton(){
    }
    private static class InnerClass{
        private static final InnerClassSinglton singlton=new InnerClassSinglton();
    }
    public InnerClassSinglton getInstance(){
        return InnerClass.singlton;
    }
}

貌似单例已经完全ok了,但是还是有一个风险,反射破坏

public class TestSingleton {
    public static void main(String[] args) throws Exception {
        Class<InnerClassSingleton> singletonClass = InnerClassSingleton.class;
        Constructor  constructor = singletonClass.getDeclaredConstructor();
        //暴力突突突破private
        constructor.setAccessible(true);
        Object instance1 = constructor.newInstance();
        Object instance2 = constructor.newInstance();
        Object instance3 = constructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);
        //....直接能new出多个实例,这怎么行
    }
}

反射直接搞出多个实例了,所以需要控制一下

//抛异常,禁用反射调用
class InnerClassSingleton {
    private static class InnerClass{
        private static final InnerClassSingleton singleton =new InnerClassSingleton();
    }
    public InnerClassSingleton getInstance(){
        return InnerClass.singleton;
    }
    private InnerClassSingleton(){
        if(InnerClass.singleton!=null){
            throw new IllegalStateException("不允许非法获取实例");
        }
    }
}

这样,你再反射直接回抛异常,真正实现了单例

但是发现每个单例类都这么搞,不得累死,所以可以使用最优雅的方式来创建单例,enum枚举

枚举

/**
 * 枚举创建单例
 */
enum Singleton {
    INSTANCE;
    private Object data = new Object();

    public Object getData() {
        return data;
    }
}

先看下这个类到底有哪些构造方法

public class SingletonTest {
    public static void main(String[] args) throws Exception {
        Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(Arrays.toString(constructor.getParameterTypes()));
        }
    }
}

可以发现有一个构造方法,参数类型为class java.lang.String, int

public class SingletonTest {
    public static void main(String[] args) throws Exception {
       /* Constructor[] constructors = Singleton.class.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(Arrays.toString(constructor.getParameterTypes()));
        }*/
        Constructor<? extends Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        Singleton singleton = constructor.newInstance("1",5);
        System.out.println(singleton);
    }
}

尝试暴力获取,发现直接报错了

java.lang.IllegalArgumentException: Cannot reflectively create enum objects

我们可以看下Construct类的newInstance

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

发现jdk已经帮我们判断了,如果发现时枚举类型,直接报错,和我们之前手动抛异常一样,这样你想反射也反射不了,保证了单例,而且也不用自己写判断逻辑,是推荐实现单例的终极方式

测试一下

public class SingletonTest {
    public static void main(String[] args) throws Exception {
        Object data1 = INSTANCE.getData();
        Object data2 = INSTANCE.getData();
        System.out.println(data1);
        System.out.println(data2);
        System.out.println(data1 == data2);
    }
}

每次获取到都是一样的,说明INSTANCE是一个单例对象,而且代码很简洁,根本不需要自己去控制线程安全不安全,也不用处理异常,jdk都帮你考虑好了,十分推荐这种单例模式

你可能感兴趣的:(并发编程)