单例模式
简介
一个类有且仅有一个实例,并且自行实例化向整个系统提供
基本用法
Kotlin 两种:不带参、带参。
Java 六种:懒汉、饿汉、双重校验锁、静态内部类、枚举和集合管理。
Kotlin 不带参
Kotlin 中使用 object
来创建单例,不允许有任何构造函数,可在 init
代码块中初始化
fun main() {
println(SingletonNormal == SingletonNormal) // init normal -> true
SingletonNormal.action() // action
}
object SingletonNormal {
init {
println("init normal")
}
}
Kotlin 带参
fun main() {
println(Manager.getInstance("hello") == Manager.getInstance("singleton")) // init hello -> true
Manager.getInstance("singleton").action() // action
}
class Manager private constructor(name: String) {
init {
println("init $name")
}
companion object : SingletonHolder<Manager, String>(::Manager)
fun action() {
println("action")
}
}
open class SingletonHolder<out T, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile
private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
饿汉式
加载时实例化
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
懒汉式
使用时实例化
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {
}
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
双重校验锁
被volatile
修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是很高。
public class SingletonLock {
private volatile static SingletonLock instance; // 关键字volatile
private SingletonLock() {
}
public static SingletonLock getInstance() {
if (instance == null) { // 先检查实例是否存在
synchronized (SingletonLock.class) { // 同步块,线程安全的创建实例
if (instance == null) { // 再次检查实例是否存在
instance = new SingletonLock();
}
}
}
return instance;
}
}
静态内部类
推荐使用
public class SingletonHolder {
private SingletonHolder() {
}
public static SingletonHolder getInstance() {
return InstanceHolder.instance;
}
private static class InstanceHolder {
private static final SingletonHolder instance = new SingletonHolder();
}
}
枚举
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。1.5中才加入enum特性
public enum SingletonEnum {
INSTANCE;
public void otherMethod() {
Log.d("SingletonEnum", String.valueOf(INSTANCE));
}
}
集合管理
ConcurrentHashMap 线程安全Map,解决并发问题
public class SingletonManager {
private static ConcurrentHashMap<String, Object> singletonMap = new ConcurrentHashMap<>();
private SingletonManager() {
}
public static void putInstance(String key, Object instance) {
singletonMap.putIfAbsent(key, instance);
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
问题
类加载器
如果单例由不同的类加载器载入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类加载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
解决方法:
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null){
classLoader = Singleton.class.getClassLoader();
}
return classLoader.loadClass(classname);
}
序列化
如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和反序列化。不管怎样,如果你序列化一个单例类的对象,接下来反序列化多个那个对象,那你就会有多个单例类的实例。
解决方法:
public class SingletonSerialize implements java.io.Serializable{
private static SingletonSerialize instance = new SingletonSerialize();
private SingletonSerialize() {
}
public static SingleSingletonSerializetonF getInstance() {
return instance;
}
// 添加此方法确保反序列化单例
private Object readResolve() throws java.io.ObjectStreamException {
return instance;
}
}