在Android开发工程中,单例模式可以说是我们使用得非常频繁的设计模式了。常见的写法有5种:
- 饿汉式
- 懒汉式
- 同步锁
- 双重校验
- 内部类
下面我们对这5种写法的Java、Kotlin各自举例。调用统一由Kotlin调用(其实差别并不大)
一、饿汉式
java:
public class BaseSingleton {
private static final String TAG = BaseSingleton.class.getSimpleName();
private BaseSingleton(){
Log.e(TAG,"BaseSingleton init");
}
private static BaseSingleton INSTANCE = new BaseSingleton();
public static BaseSingleton getInstance(){
return INSTANCE;
}
public void sayHello(String name){
Log.e(TAG,"Hello "+name+",nice to meet you !");
}
}
Kotlin:
object KBaseSingleton {
@JvmStatic
fun sayHello(name:String){
Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
}
}
是不是觉得Kotlin特别的简单?是的,Kotlin一个Object就可以标记一个单例了。
调用:
BaseSingleton.getInstance().sayHello("Calm")
KBaseSingleton.sayHello("Calm")
可以看到这种写法很简单,但静态变量的代码在类加载则会执行,不管后续是否会使用,单例就会存在了。如果程序一定会用到这个单例,则此写法适用。
二、懒汉式
Java:
public class LazySingleton {
private static final String TAG = BaseSingleton.class.getSimpleName();
private LazySingleton(){
Log.e(TAG,"LazySingleton init");
}
private static LazySingleton INSTANCE;
public static LazySingleton getInstance(){
if(INSTANCE == null){
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
public void sayHello(String name){
Log.e(TAG,"Hello "+name+",nice to meet you !");
}
}
Kotlin:
class KLazySingleton private constructor(){
companion object {
//原生写法
val INSTANCE by lazy(LazyThreadSafetyMode.NONE) {
KLazySingleton()
}
//翻译Java
private var INSTANCE1:KLazySingleton? = null
fun getInstance():KLazySingleton{
if(INSTANCE1 == null){
INSTANCE1 = KLazySingleton()
}
return INSTANCE1!!
}
}
fun sayHello(name:String){
Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
}
}
可以看到此种写法静态变量开始并不赋值,在调用时进行空判断并进行赋值。那么如果我们同时多个线程来调用,是否如我们所希望只有一个实例呢?我们写如下调用代码:
for (i in 0..20){
Thread(
Runnable {
LazySingleton.getInstance().sayHello("Calm$i")
Log.e(this::class.java.simpleName,Thread.currentThread().id.toString())
}
).start()
}
循环开启20个线程来调用单例,其输出结果如下:
10-12 14:07:33.920 8361-8470/com.calm.singleton E/LazySingleton: LazySingleton init
10-12 14:07:33.922 8361-8470/com.calm.singleton E/LazySingleton: Hello Calm0,nice to meet you !
10-12 14:07:33.924 8361-8471/com.calm.singleton E/LazySingleton: LazySingleton init
10-12 14:07:33.925 8361-8471/com.calm.singleton E/LazySingleton: Hello Calm1,nice to meet you !
10-12 14:07:33.926 8361-8470/com.calm.singleton E/MainActivity: 4614
10-12 14:07:33.926 8361-8471/com.calm.singleton E/MainActivity: 4615
这里我只截取了出现问题的部分,可以看到由于多线程调用而导致单例其实是被初始化了2次的,存在2个实例,而不是我们希望的单例。可见此种写法如果要用于多线程,是不合适的。
三、同步锁
Java:
public class LazySyncSingleton {
private static final String TAG = LazySyncSingleton.class.getSimpleName();
private LazySyncSingleton(){
Log.e(TAG,"LazySyncSingleton init");
}
private static LazySyncSingleton INSTANCE;
public static synchronized LazySyncSingleton getInstance(){
if(INSTANCE == null){
INSTANCE = new LazySyncSingleton();
}
return INSTANCE;
}
public void sayHello(String name){
Log.e(TAG,"Hello "+name+",nice to meet you !");
}
}
Kotlin:
class KLazySyncSingleton {
companion object {
private var INSTANCE:KLazySyncSingleton? = null
@Synchronized
fun getInstance():KLazySyncSingleton{
if(INSTANCE == null){
INSTANCE = KLazySyncSingleton()
}
return INSTANCE!!
}
}
fun sayHello(name:String){
Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
}
}
可以看到,其实此种方式与懒汉式区别只是加上了@Synchronized 标识,完美解决多线程而导致的非单例问题。只是在每次获取单例的时候都加了锁,效率上有所牺牲。于是,第四种写法应运而生。
四、双重校验
Java:
public class DoubleCheckSingleton {
private static final String TAG = DoubleCheckSingleton.class.getSimpleName();
private DoubleCheckSingleton(){
Log.e(TAG,"DoubleCheckSingleton init");
}
private static volatile DoubleCheckSingleton INSTANCE;
public static DoubleCheckSingleton getInstance(){
if(INSTANCE == null){
synchronized (DoubleCheckSingleton.class){
if(INSTANCE == null){
INSTANCE = new DoubleCheckSingleton();
}
}
}
return INSTANCE;
}
public void sayHello(String name){
Log.e(TAG,"Hello "+name+",nice to meet you !");
}
}
Kotlin:
class KDoubleCheckSingleton {
companion object {
//原生写法
val INSTANCE by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
KDoubleCheckSingleton()
}
//翻译写法
@Volatile
private var INSTANCE1:KDoubleCheckSingleton? = null
fun getInstance():KDoubleCheckSingleton{
if(INSTANCE1 == null){
synchronized(DoubleCheckSingleton::class){
if(INSTANCE1 == null){
INSTANCE1 = KDoubleCheckSingleton()
}
}
}
return INSTANCE1!!
}
}
fun sayHello(name:String){
Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
}
}
此种写法较同步锁写法,只在第一次获取单例的时候加锁,之后则不需要在加锁,提升了第一次之后获取单例的效率。
五、内部类
Java:
public class InnerSingleton {
private static final String TAG = InnerSingleton.class.getSimpleName();
private InnerSingleton(){
Log.e(TAG,"InnerSingleton init");
}
private static class Holer{
private static InnerSingleton INSTANCE = new InnerSingleton();
}
public static InnerSingleton getInstance(){
return Holer.INSTANCE;
}
public void sayHello(String name){
Log.e(TAG,"Hello "+name+",nice to meet you !");
}
}
Kotlin:
class KInnerSingleton {
private object Holder{
val INSTANCE = KInnerSingleton()
}
companion object {
fun getInstance() = Holder.INSTANCE
}
fun sayHello(name:String){
Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
}
}
惊到了没?依靠静态内部类保证了线程同步,又满足了在getInstance()时才创建单例的懒汉式。比较推荐的一种写法。
单例的写法具体使用哪一种,还是要依赖于我们实际的使用场景来进行选择,而不是一味的哪一种好。其实他们都有各自的优缺点,依场景选择即可。