单例模式是应用最多的设计模式之一。在整个运行周期,单例模式可保证只有一个实例存在。下面是几种常见的单例模式的实现。
懒汉模式
懒汉模式在第一次获取时才会实例化
public class SingleTon {
//静态变量
private static volatile SingleTon instance;
//私有化,防止外部调用
private SingleTon() {
}
public static SingleTon getInstance() {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}
Double Check Lock实现单例
多加了一次检测,原因在于new 对象并非原子操作,这样可避免一个线程调用getInstance方法在new对象的过程中(尚未完成)另外一个线程也调了getInstance方法生成新的对象
public class SingleTon {
//静态变量
private static volatile SingleTon instance;
//私有化,防止外部调用
private SingleTon() { }
public static SingleTon getInstance() {
if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
饿汉模式
在类加载的时候实例化
public class SingleTon {
//静态变量
private static final SingleTon instance = new SingleTon();
//私有化,防止外部调用
private SingleTon() {
}
public static SingleTon getInstance() {
return instance;
}
}
静态内部类
第一次调用getInstance方法才会加载内部类SingleTonHolder
public class SingleTon {
//私有化,防止外部调用
private SingleTon() {
}
public static SingleTon getInstance() {
return SingleTonHolder.instance;
}
private static class SingleTonHolder{
private static final SingleTon instance = new SingleTon();
}
}
枚举实现单例
实际上,下例中枚举类型在编译之后是一个继承了java.lang.Enum类的普通类SingleTon,INSTANCE最终是public static final SingleTon类型的对象,本质上是饿汉模式,但是可防止反序列化,所以 比饿汉模式更好。
public enum SingleTon {
INSTANCE;
}
CAS方式实现线程安全的单例
通过CAS的方式,来实现不加锁的懒汉单例。但是这种方式如果实例化前很多个线程同时进来,会多个线程处于死循环状态,导致CPU占用率大。
public class CASSingleTon {
private static final String TAG = "CASSingleTon";
private static AtomicBoolean otherThreadInit = new AtomicBoolean(false);
private volatile static CASSingleTon singleTon ;
private CASSingleTon() {
Log.i(TAG, "CASSingleTon: new");
}
public static CASSingleTon getInstance() {
while (singleTon == null) {
//如果有其他线程先进来做初始化了,则当前线程死循环等待
if (otherThreadInit.compareAndSet(false, true)) {
if(singleTon == null){
singleTon = new CASSingleTon();
}
}
}
return singleTon;
}
}
其实,上面的代码存在的问题还可以继续优化一下
public static class CASSingleTon {
private static final String TAG = "CASSingleTon";
private static AtomicBoolean otherThreadInit = new AtomicBoolean(false);
private volatile static CASSingleTon singleTon ;
private CASSingleTon() {
Log.i(TAG, "CASSingleTon: new");
}
public static CASSingleTon getInstance() {
while (singleTon == null) {
//如果有其他线程先进来做初始化了,让出资源
if (otherThreadInit.get())
Thread.yield();
else if (otherThreadInit.compareAndSet(false, true)) {
if(singleTon == null){
singleTon = new CASSingleTon();
break;
}
}
}
return singleTon;
}
}
有没有觉得上面这段代码的写法很眼熟,其实它就是ConcurrentHashMap数组的初始化的写法。所以说平时多看源码还是很有用的
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}