单例模式的九种实现方式

单例模式

单例简介

因进程需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象。
单例模式的特点:
1.单例模式只能有一个实例。
2.单例类必须创建自己的唯一实例。
3.单例类必须向其他对象提供这一实例。

懒汉式

特点:
线程不安全,如果多线程同时访问,会创造出多个对象。

/**
 * 通过提供一个静态的对象 SINGLE_TON_DEMO_1
 * 利用private修饰的构造方法和getInstance()提供一个单例
 *
 * 缺点:线程不安全,如果多个线程同时访问,会构造出多个对象
 */
public class SingleTonDemo1 {
    private static  SingleTonDemo1 SINGLE_TON_DEMO_1 ;
    private SingleTonDemo1(){}

    public static SingleTonDemo1 getInstance(){
        if (SINGLE_TON_DEMO_1==null) {
            SINGLE_TON_DEMO_1 = new SingleTonDemo1();
        }
        return SINGLE_TON_DEMO_1;
    }

}

实例化方法加synchronized的懒汉式

特点:
线程安全,但是效率不高。
由于并发并不是随时都在发生,大多数情况下这个锁占用的资源都浪费了。

/**
 * 并发不是随时都在发生,大多数时候这个锁占用的资源都浪费了
 * 虽然线程安全,但是效率不高
 */
public class SingleTonDemo2 {
    private static SingleTonDemo2 singleTonDemo2;
    private SingleTonDemo2(){}

    public static synchronized SingleTonDemo2 getInstance(){
        if (singleTonDemo2 == null) {
            singleTonDemo2 = new SingleTonDemo2();
        }
        return singleTonDemo2;
    }
}

饿汉式

特点:
线程安全

/**
 *  线程安全
 *  相比于静态方类,内存常驻
 */
public class SIngleTonDemo3 {

    private static SIngleTonDemo3 sIngleTonDemo3 = new SIngleTonDemo3();
    private SIngleTonDemo3(){}

    public static SIngleTonDemo3 getInstance(){
        return sIngleTonDemo3;
    }
}

静态内部类加载

特点:
1.线程安全
2.使用静态内部类的好处是,不会随着类的加载而加载,而是在调用getInstance()方法时再加载,达到类似懒汉模式的效果,而这种方式是线程安全的

/**
 * 线程安全
 *
 * 使用静态内部类的好处是,不会随着类的加载而加载,而是在调用getInstance()方法时
 * 再加载,达到类似懒汉模式的效果,而这种方式是线程安全的
 */
public class SingleTonDemo4 {
    public static class SingleTonHolder{
        private static SingleTonDemo4 singleTonDemo4 = new SingleTonDemo4();
    }

    private SingleTonDemo4(){}

    public static SingleTonDemo4 getInstance(){
        return SingleTonHolder.singleTonDemo4;
    }


}

枚举方法

特点:
1.线程安全
2.自由串行化
3.保证只有一个单例
4.Effective java 作者Josh Bloch提倡的方式

/**
 * Effective java 作者Josh Bloch提倡的方式,解决了以下三个问题
 * 一、自由串行化
 * 二、保证只有一个单例
 * 三、线程安全
 */
public enum  SingleTonDemo5 {
    INSTANCE;

    public static void main(String[] args) {
        SingleTonDemo5 instance = SingleTonDemo5.INSTANCE;

    }


}

双重校验锁

特点:
1.通常线程安全,低概率线程不安全
2.并发情况下,可能会出现线程未拿到实例返回null的情况。

/**
 * 通常线程安全,低概率线程不安全
 *
 * 当并发情况下
 * 一、线程A进入getInstance()方法,此时单例还没有实例化,进入了锁定块
 * 二、线程B进入getInstance()方法,此时单例还没有实例化,得以访问接下来代码块,但是代码块已经被线程A锁定
 * 三、线程A进入下一判断,因为单例还没有实例化,所以实例化单例,实例化后退出代码块,解除锁定
 * 四、线程B进入下一判断,此时单例已经被实例化,退出代码块,解除锁定
 * 五、线程A拿到单例实例并返回,线程B未拿到实例返回null
 *
 */
public class SingleTonDemo6 {

    private static SingleTonDemo6 singleTonDemo6;

    private SingleTonDemo6(){}

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

加volatile的双重校验锁

特点:
1.volatile关键字此处的作用是防止指令重排

/**
 * volatile关键字此处的作用是防止指令重排,把singleTonDemo7声明为volatile后,对它的写操作就会
 * 有一个内存屏障,这样在它赋值完成之前,都不会调用读操作。
 *
 * 注意:volatile阻止的不是singleTonDemo7 = new SingleTonDemo7(),而是在写操作完成之前,不会调用
 * if (singleTonDemo7 == null)这个读操作
 */
public class SingleTonDemo7 {

    private static volatile SingleTonDemo7 singleTonDemo7;

    private SingleTonDemo7(){}

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

}

ThreadLocal

特点:
线程安全
隔离多个线程对数据的访问冲突

/**
 * 线程安全
 * ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突
 *
 *
 */
public class SingleTonDemo8 {

    private static final ThreadLocal<SingleTonDemo8> THREAD_LOCAL =
            new ThreadLocal<SingleTonDemo8>(){
                @Override
                protected SingleTonDemo8 initialValue() {
                    return new SingleTonDemo8();
                }
    };
    private SingleTonDemo8(){}

    public static SingleTonDemo8 getInstance(){
        return THREAD_LOCAL.get();
    }
}

CAS锁

特点:
线程安全

/**
 * 线程安全
 *
 * CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
 *更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
 * CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新
 *
 * 缺点:
 * CPU开销较大
 * CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性
 */
public class SingleTonDemo9 {

    private static final AtomicReference<SingleTonDemo9> ATOMIC_REFERENCE = new AtomicReference<>();

    private SingleTonDemo9(){}

    public static final SingleTonDemo9 getInstance(){
        // 相当于while(true)
        for (;;){
            SingleTonDemo9 currentDemo9 = ATOMIC_REFERENCE.get();
            if (currentDemo9 != null){
                return currentDemo9;
            }
            currentDemo9 = new SingleTonDemo9();
            if (ATOMIC_REFERENCE.compareAndSet(null,currentDemo9)){
                return currentDemo9;
            }
        }
    }

    public static void main(String[] args) {
        SingleTonDemo9 testSingleTon = new SingleTonDemo9();
        SingleTonDemo9 testSingleTon2 = new SingleTonDemo9();
        System.out.println(testSingleTon == testSingleTon2);    //false
    }
}

Github地址


SingleTonDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章