同算法与数据结构一样,设计模式同样是作为一个优秀程序员必须熟练掌握与应用的基本功,在阅读源码或者优秀的开源项目的同时,不难发现各种设计模式的灵活运用,叹为观止。
接下来一系列博客,将和大家一起分享这些常用的设计模式。
本篇一起来看看单例模式,单例模式即在一个系统中,要求一个类有且仅有一个对象,例如
- 整个项目需要一个共享访问点或共享数据
- 创建一个对象需要耗费的资源过多,比如访问I/O或者数据库等资源
- 工具类对象
等等,单例模式有6种常见的写法,接下来一起来看看这些写法
饿汉模式(不推荐)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
在类加载的时候就完成了初始化,类加载较慢,线程安全,但没有达到懒加载的效果,如果从未使用过此实例,则会造成内存浪费。
懒汉模式(不推荐)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在使用的时候才初始化,不过在第一次初始化的时候速度会慢一点,而且更重要的是在多线程模式下不能正常工作。
线程安全的懒汉模式(不推荐)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在方法中使用同步锁,确实可以避免线程不安全的问题,但是每次加锁都会造成不必要的开销。
DLC(推荐)
public class Singleton {
private static volatile Singleton mInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (mInstance == null) { //1
synchronized (Singleton.class) { // 2
if (mInstance == null) { // 3
mInstance = new Singleton(); //4
}
}
}
return mInstance;
}
}
DLC相较于前面的写法较为复杂,在代码中加了注释1,2,3,4,注释一处的判空是为了避免不必要的同步操作。在多线程的情况下,只有1,没有2、3,有可能会创建多个实例,有2没有3,还是有可能会创建多个实例。
例如A线程和B线程在得到时间片时分别执行了1步骤,接下来的执行过程中,2保证了线程安全,3保证了这种情况下的唯一实例。
volatile关键字在这里的主要作用是防止指令的重排序,大家都知道new对象并非原子操作,要经历
看class对象是否加载,如果没有就先加载class对象
分配内存空间,初始化实例
调用构造函数
返回地址给引用
这四个步骤,而cpu为了优化程序,可能会进行指令重排序,打乱这3,4这几个步骤,导致实例内存还没分配,就被使用了。
例如线程A执行到new Singleton(),开始初始化实例对象,由于存在指令重排序,这次new操作,先把引用赋值了,还没有执行构造函数。这时时间片结束了,切换到线程B执行,线程B调用new Singleton()方法,发现引用不等于null,就直接返回引用地址了,然后线程B执行了一些操作,就可能导致线程B使用了还没有被初始化的变量。
静态内部类(推荐)
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder并初始化instance。这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
枚举(可读性不高,不推荐)
public enum Singleton {
INSTANCE;
public void doSomething() {
}
}
静态类和单例模式的区别
静态类即所有方法都说是静态方法,并且构造方法被private修饰不能实例化的类,如Math就是一个静态类,那么静态类和单例模式究竟有什么区别呢?
1)单例模式会给你一个全局唯一的对象, 而静态类会给你很多静态方法,这些方法发可以通过类名直接调用
2)单例模式灵活性更高,方法可以被重写,而静态类都是静态方法,不能被重写
3)如果是一个非常重的对象,单例模式可以懒加载,而静态类无法做到
Kotlin代码
//饿汉
object Singleton {
val instance = Singleton()
}
//懒汉
object Singleton {
private var instance: Singleton? = null
fun getInstance(): Singleton? {
if (instance == null) {
instance = Singleton
}
return instance
}
}
//线程安全懒汉
object Singleton {
private var instance: Singleton? = null
@Synchronized
fun getInstance(): Singleton {
if (instance == null) {
instance = Singleton
}
return instance as Singleton
}
}
//DLC
object Singleton {
@Volatile
private var mInstance: Singleton? = null
val instance: Singleton?
get() {
if (mInstance == null) {
synchronized(Singleton::class.java) {
if (mInstance == null) {
mInstance = Singleton
}
}
}
return mInstance
}
}
//静态内部类
class Singleton private constructor() {
val instance: Singleton
get() = SingletonHolder.instance
private object SingletonHolder {
val instance = Singleton()
}
}
//枚举
enum class Singleton {
INSTANCE;
fun doSomething() {
}
}