几种常用的单例模式以及volatile在单例中的使用

单例模式详解:volatile与线程安全
本文探讨了单例模式的多种实现方式,包括枚举类、懒汉模式1、懒汉模式2(双重检查锁定)、懒汉模式3(推荐),以及饿汉模式和持有类模式。重点讲解了懒汉模式3中volatile关键字防止指令重排以确保线程安全的重要性。总结推荐使用懒汉模式3、饿汉模式和持有类模式。

使用枚举类:

public enum SingleTonEnum {
    INSTANCE;
    private String str;

    SingleTonEnum() {

    }

    public void setStr(String str) {
        this.str = str;
    }

    public String getStr() {
        return str;
    }
}

使用:

private void testSingleTonEnum() {
        SingleTonEnum singleTonEnum1 = SingleTonEnum.INSTANCE;
        SingleTonEnum singleTonEnum2 = SingleTonEnum.INSTANCE;
        singleTonEnum1.setStr("123");
        Log.i(TAG, singleTonEnum1.getStr() + "  " + singleTonEnum2.getStr());
        singleTonEnum2.setStr("456");
        Log.i(TAG, singleTonEnum1.getStr() + "  " + singleTonEnum2.getStr());
        Log.i(TAG, (singleTonEnum1 == singleTonEnum2) + "");
}


测试结果:

懒汉模式1:

public class SingleTon1 {
    private static SingleTon1 mInstance = null;

    private SingleTon1() {

    }

    public static SingleTon1 getInstance() {
        if (mInstance == null) {
            mInstance = new SingleTon1();
        }
        return mInstance;
    }

}

    懒汉模式1不是线程安全的,有可能在应用进程中出现多个实例,比如线程A在调用getInstance()时发现mInstance为null,会创建实例,此时线程B也同时调用了getInstance()同样发现mInstance为null,也会创建一个实例,会造成应用进程内存在多个实例对象。

懒汉模式2:

public class SingleTon1 {
    private static SingleTon1 mInstance = null;

    private SingleTon1() {

    }

    public synchronized static SingleTon1 getInstance() {
        if (mInstance == null) {
            mInstance = new SingleTon1();
        }
        return mInstance;
    }
}

懒汉模式2虽然是线程安全的,但是每次调用getInstance()时都进行同步,存在很大的性能损耗。

懒汉模式3:(常用方式,推荐)

public class SingleTon1 {
    //volatile很关键,禁止jvm指令重排
    private static volatile SingleTon1 mInstance = null;

    private SingleTon1() {

    }

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

三个关键点:

1、第一处判空

第一次null检查确保了只有第一次调用getInstance时才会做同步,如果去掉第一次null检查,每次调用getInstance()都会执行synchronized(SingleTon1.class),这样的话就和懒汉模式2变得一模一样了,降低程序执行效率。

2、第二处判空

第二次null检查预防两个线程同时调用getInstance()时生成两个实例对象,当两个线程同时调用getInstance()时,线程A获得了类锁,线程B阻塞,线程A第二次判null为true,new SingleTon1(),线程A释放类锁,线程B获得类锁,此时如果没有第二次判null,线程B也会创建一个实例对象,会生成两个实例对象。

3、volatile禁止jvm指令重排

new SingleTon1()并不是原子操作,在jvm执行字节码指令时,new SingleTon1()分成了如下三步:

memory = allocate();    //1:分配对象的内存空间
initInstance(memory);   //2:初始化对象,成员变量初始化、执行非静态代码块
instance = memory;      //3:设置instance指向刚分配的内存地址

上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM可以以“优化”为目的对它们进行重排序,经过重排序后如下:

memory = allocate();    //1:分配对象的内存空间
instance = memory;      //3:设置instance指向刚分配的内存地址(此时对象还未初始化,成员变量还未赋值)
initInstance(memory);   //2:初始化对象

指令重排后,引用instance指向内存memory时,这段崭新的内存还没有初始化——即,引用instance指向了一个还没有初始化的对象。此时,如果另一个线程调用getInstance方法,由于instance已经指向了一块内存空间,从而if条件判为false,方法返回instance引用,该线程得到了没有完成初始化的对象。

使用volatile禁止jvm指令重排,必须按照上述1、2、3步骤执行,从而杜绝上述情况出现。

饿汉模式:

public class SingleTon1 {
    private static SingleTon1 mInstance = new SingleTon1();

    private SingleTon1() {

    }

    public static SingleTon1 getInstance() {
        return mInstance;
    }
}


1 线程安全

2 在SingleTon1类加载时,就创建了静态实例

使用持有类:

public class SingleTon1 {
    private SingleTon1() {

    }

    private static final class InstanceHolder

    {
        private static SingleTon1 mInstance = new SingleTon1();
    }

    public static SingleTon1 getInstance() {
        return InstanceHolder.mInstance;
    }
}

1 线程安全

2 不会在类加载时就创建SingleTon1的实例。

综上:懒汉模式3、饿汉模式、持有类模式,这三种推荐使用。

希望大家批评指正
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值