单例模式是校招中最常考的
设计模式之⼀
啥是设计模式?设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏.软件开发中也有很多常⻅的 "问题场景". 针对这些问题场景, ⼤佬们总结出了⼀些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。
这一点在很多场景上都需要,比如JDBC中的DataSource实例就只需要一个
单例模式具体的实现⽅式有很多. 最常⻅的是 "饿汉" 和 "懒汉" 两种.
饿汉模式
类加载的同时, 创建实例.
class Singleton{
private static Singleton instance = new Singleton();//()内可加数字//静态成员的初始化是在类加载的阶段触发的
public static Singleton getInstance(){
return instance;
}
private Singleton(){
}//单例模式中的 点睛之笔 在类外进行new操作都会编译失败
private Singleton(int n){
}
}
public class Main {
public static void main(String[] args) {
Singleton t1 = Singleton.getInstance();
Singleton t2 = Singleton.getInstance();
System.out.println(t1 == t2);
//Singleton t3 = new Singleton();
}
}
输出结果如下:
懒汉模式-单线程版
类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.
class SingletonLazy{
private static SingletonLazy instance = null;
private SingletonLazy(){
}
public static SingletonLazy getInstance(){
if (instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
public class Demo29 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
}
}
懒汉模式-多线程版
上⾯的懒汉模式的实现是线程不安全的.
线程安全问题发⽣在⾸次创建实例时. 如果在多个线程中同时调⽤ getInstance ⽅法, 就可能导致创建出多个实例.⼀旦实例已经创建好了, 后⾯再多线程环境调⽤ getInstance 就不再有线程安全问题了(不再修改instance 了)
加上 synchronized 可以改善这⾥的线程安全问题。
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static SingletonLazy getInstance3(){
synchronized (locker){//当把实例创建好后,后续再调用getInstance,此时都是直接执行return
if (instance == null){//但是,每次调用上述的方法,都会触发一次加锁操作,虽然不涉及线性安全问题
instance = new SingletonLazy();//多线程情况下,这里的加锁就会相互阻塞,影响程序运行效率
}
}
return instance;
}
public static SingletonLazy getInstance2(){
if (instance == null){
synchronized (locker){
instance = new SingletonLazy();
}
}
return instance;
}
public synchronized static SingletonLazy getInstance1(){
if (instance == null){
instance = new SingletonLazy();
}
return instance;
}
以上有3种方法,不过还是存在一定的BUG!!!
懒汉模式-多线程版(改进)
以下代码在加锁的基础上, 做出了进⼀步改动:
• 使⽤双重 if 判定, 降低锁竞争的频率.
• 给 instance 加上了 volatile.
class SingletonLazy{
private static volatile SingletonLazy instance = null;//预防内存可见性问题
//volite的功能: 确保每次读取操作都是读内存 关于该变量的读取和修改操作不会触发重排序
//private static SingletonLazy instance = null;
private static Object locker = new Object();
private SingletonLazy(){
}
public static SingletonLazy getInstance(){//优化
if (instance == null){//这个if是判断是否需要加锁
synchronized (locker){
if (instance == null){//这个if是判断是否需要new 对象
instance = new SingletonLazy();
}
}
}
return instance;
}
}
理解双重 if 判定 / volatile:加锁 / 解锁是⼀件开销⽐较⾼的事情. ⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候. 因此后续使⽤的时候, 不必再进⾏加锁了.外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.同时为了避免 "内存可⻅性" 导致读取的 instance 出现偏差, 于是补充上 volatile .当多线程⾸次调⽤ getInstance, ⼤家可能都发现 instance 为 null, 于是⼜继续往下执⾏来竞争锁, 其 中竞争成功的线程, 再完成创建实例的操作.当这个实例创建完了之后, 其他竞争到锁的线程就被⾥层 if 挡住了. 也就不会继续创建其他实例.1. 有三个线程, 开始执⾏ getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同⼀把锁.
2. 其中线程1 率先获取到锁, 此时线程1 通过⾥层的 if (instance == null) 进⼀步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.
3. 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过⾥层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.
4. 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了,从⽽不再尝试获取锁了. 降低了开销.![]()