1.啥是设计模式?
设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有
一些固定的套路. 按照套路来走局势就不会吃亏.软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
- 单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.
单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种.
2.饿汉模式
类加载的同时, 创建实例.
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
3.懒汉模式
类加载的时候不创建实例. 第一次使用的时候才创建实例.
class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
package thread3;
//单例模式
//饿汉模式
class SingletonDataSource {
private static SingletonDataSource instance = new SingletonDataSource();
//外面的类无法调用该类的构造方法(别的类无法创建实例了)
private SingletonDataSource() {
}
//一方面把程序中要用到的实例准备好
//一方面把外部构造实例的口子给封上
//留下一个通道,让外界能访问到 instance 这个唯一对象 (外界访问该实例的唯一通道)
public static SingletonDataSource getInstance() {
return instance;
}
}
//懒汉模式
//和饿汉模式相比,懒汉模式,主要的差别就在于这个实例的创建时机不同了,不在是类加载的时候就立即创建实例
//而是在首次调用到getInstance的时候,才会真正创建实例
class SingletonDataSource2 {
private static SingletonDataSource2 instance = null;
private SingletonDataSource2() {
}
public static SingletonDataSource2 getInstance() {
if (instance == null) {
instance = new SingletonDataSource2();
}
return instance;
}
}
public class Test9 {
public static void main(String[] args) {
//无论在代码中的哪个地方来调用这里的getInstance得到的都是同一个实例
SingletonDataSource dataSource = SingletonDataSource.getInstance();
}
}
4.饿汉模式-懒汉模式 线程安全问题
对于饿汉模式来说,多线程调用getInstance,只是针对同一个变量来“读”,线程是安全的!
但是对于懒汉模式来说,多线程调用getInstance,大部分情况是读,但是会修改内容,这就会导致线程不安全!
4.懒汉模式-多线程版
上面的懒汉模式的实现是线程不安全的
线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.
一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改instance 了)
加上 synchronized 可以改善这里的线程安全问题
class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
5.懒汉模式-多线程版(改进,终极版)
以下代码在加锁的基础上, 做出了进一步改动:
- 使用双重 if 判定, 降低锁竞争的频率.
- 给 instance 加上了 volatile.
class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
理解双重 if 判定 / volatile:
加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.同时为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile .当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作.当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
5.写一个线程安全的单例模式(终极版)
package thread3;
//单例模式
//饿汉模式
class SingletonDataSource {
private static SingletonDataSource instance = new SingletonDataSource();
//外面的类无法调用该类的构造方法(别的类无法创建实例了)
private SingletonDataSource() {
}
//一方面把程序中要用到的实例准备好
//一方面把外部构造实例的口子给封上
//留下一个通道,让外界能访问到 instance 这个唯一对象 (外界访问该实例的唯一通道)
public static SingletonDataSource getInstance() {
return instance;
}
}
//懒汉模式
//和饿汉模式相比,懒汉模式,主要的差别就在于这个实例的创建时机不同了,不在是类加载的时候就立即创建实例
//而是在首次调用到getInstance的时候,才会真正创建实例
class SingletonDataSource2 {
private volatile static SingletonDataSource2 instance = null;
private SingletonDataSource2() {
}
public static SingletonDataSource2 getInstance() {
if (instance == null) {
synchronized (SingletonDataSource2.class) {
if (instance == null) {
instance = new SingletonDataSource2();
}
}
}
return instance;
}
}
public class Test9 {
public static void main(String[] args) {
//无论在代码中的哪个地方来调用这里的getInstance得到的都是同一个实例
SingletonDataSource dataSource = SingletonDataSource.getInstance();
}
}