@(设计模式)
类图
简介
单例模式,可以说是设计模式中最简单的模式之一了,因为它不是用来解耦合用的,而是为了让整个系统有且只有一个这样的对象。为什么要只有一个呢??如果有些对象对象非常大,我需要保证内存中只存在一个;如果我希望做一些控制,只能由一个事例对象来控制,都会用到单例模式。
接下来我们就由浅入深的来探索单例模式的写法:
问题
首先我们需要来考虑2件事情,既然单例模式需要保证系统中最多只有一个这样的对象事例,那么我怎么才能确保只能有一个实例呢??
- 构造方法:如果构造方法是 public,那么任何类都可以 new 我的对象,不能保证单例,所以单例模式构造方法一定是 private的
- 如果构造方法是 private的,那么我怎么 new对象呢?说明我只能在我自己的类中,先new出自己,然后提供给外部。
- 由于系统只有一个单例对象,那么线程同步是一定需要的,怎么来做呢?
就是因为上面的第2步,我们就出现了很多单例模式的写法,下面我们一一介绍:
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
client
Singleton s = Singleton.getInstance();
问题1
- 为什么 构造方法是 private的? 因为上面提到的,不能由其他类任意new单例模式的类
- 为什么
getInstance()
是 static方法,因为用了类名.
来调用getInstance() 方法。 - 为什么 instance 是 static的? 静态方法可以访问非静态变量吗?显然不行,所以instance也是static的,而且静态变量是不是只会被初始化一次(感觉3个问题都是java考试题一样,哈哈哈!)
饱汉式
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance() {
if(null == instance){
instance = new Singleton();
}
return instance;
}
}
问题2
与饿汉式的不同:不是一看到 instance 就初始化,饱汉要等到第一次使用的时候才初始化,不像饿汉一样一见到 instance 就初始化,这也被称为 懒加载,如果系统中很多这样的类,显然是懒加载的时候效率更高
线程安全
如果对线程学得还行的同学就应该知道,上面的饱汉式写法其实不是那么的安全!!虽然在 getInstance() 中有非空判断,但是线程这个东西是由时间片控制的,如果一个线程刚刚非空判断通过了,然后切换到下个线程,也执行并通过非空判断,并切换到上一个线程,其实两个线程都会执行new操作,就不单例了!(虽然发生概率不高)但是我们为了更加的准确,我们来看看线程安全的版本怎么写
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance() {
if(null == instance){
instance = new Singleton();
}
return instance;
}
}
问题3
只是在 getInstance() 前添加了 synchronized 关键字,保证同一时刻只能有一个线程执行获取到类锁,并执行创建实例代码,保证了只会创建一次。同时也有了懒加载的特性。
但是但是, 由于是单例模式,如果将锁直接锁在 static 的方法上(相当于类锁,其他static方法也不能执行),那么效率是非常低的,所以我们还有一定的改进。
双重检查加锁
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
这个版本看出优秀在哪里了吗?
- 懒加载
- 确保线程安全
- 只有第一次创建类的时候可能发生阻塞,后面由于非空判断都不会阻塞
问题4
为什么有两次非空判断?
还是那个问题,时间片这个东西是不稳定的,由于线程切换,有可能两个线程同时都过掉第一层 非空判断,如果第二层不加,还是有可能调用2次new。为什么是 synchronized (Singleton.class) 而不是 synchronized (this)?
有时java基本功,getInstance() 是 static 方法!static 方法!static 方法!,所以可以用this吗?
总结
感觉这篇不是在讲设计模式,在讲java基础,呵呵!为什么会有问题1,2,3,4,其实这些问题也是我以前别面过的,一个单例模式就能考这么多东西,希望对大家有一定帮助。