使用枚举类:
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、饿汉模式、持有类模式,这三种推荐使用。
希望大家批评指正