单例模式——饿汉模式和懒汉模式

目录

  • 线程安全的单例模式
    • 饿汉模式
    • 懒汉模式
      • 懒汉模式总结

线程安全的单例模式

线程安全的单例模式是面试中常见的问题,所以熟练掌握这种单例模式尤为重要
什么叫单例模式?
单例模式就是一种设计模式,写代码时一种常见的应用场景,设计模式就是针对于这些应用场景给出的解决方案,我们可以把它想成为棋谱,这种棋谱就是由那些“围棋高手”(大佬程序猿)留下来便于小白使用
而且我们还需要知道单例模式的效果就是保证某个类只有唯一实例

单例模式分为饿汉模式和懒汉模式

饿汉模式

顾名思义,饿汉模式就是表示比较着急的去干某件事,这里的饿汉并不是代表真的饿了,而是代表急不可待的意思
举个 :小明假期留了三本大作业,结果放假第一天,小明就照着答案把这些作业全都写完了,于是小明的假期就变的甚是潇洒,显然,这种做法是不可取的,虽然速度提上去了,但是并没有什么卵用

我么可以引出饿汉的单例模式就是比较着急的去创建线程

下面我们就用代码来演示一下饿汉的单例模式

创建一个Singleton类(我们要知道singleton这个单词的意思,如果singleton大家不知道那么singledog的意思大家一定要知道,没错就是你“单身狗 的意思”,那么singleton就是单个的意思),通过这个类来保证单例模式只有唯一的实例

*
线程安全的单例模式实现-饿汉模式
 */
    class Singleton{
        //1.使用static创建一个实例,并且立即进行实例化,
        //这个instance对应的实例,是该类的唯一实例.
        private static Singleton instance = new Singleton();
        //2.为了防止程序猿在其他的地方不小心的new了这个Singleton,把构造方法设为private
        private Singleton(){}
        //3.提供一个方法,让类外能拿到这个唯一实例
        public static Singleton getInstance(){
            return instance;
        }
}
public class TestDemo1 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
    }
}

上述代码中,static是用来修饰类成员(类属性/类方法),一个Java程序中,一个类对象只存在一份(JVM保证的),这样也就保证了static修饰的实例只有一份了
static是单例模式实现的主要方法

懒汉模式

举 :小红和小明是同班同学,小明为了能够愉快的过假期,一天的时间内写完了所有的作业,小红不同,小红是个好学生,她给自己定了个计划,每天写一点,而且很认真独立完成作业,在照着答案批改,写完作业后再去玩,就这样,小红用了一个假期写完了所有作业,开学后,小明受了老师的惩罚,小红却得到了表扬

写到这里,可能就会有同学会问,那为什么把这种模式叫做懒汉模式呢
我们需要知道,在计算机中是一种褒义词,并不是我们日常生活中的懒,
这种懒会让系统变得更高效,系统什么时候有时间,系统再去工作,而不是一股脑的把工作全给做完

我们可以看出懒汉模式的单例模式就是什么时候需要去做的时候,系统才回去创建实例,而不是一股脑的把实例全都创建出来

下面我们就用代码来演示一下懒汉的单例模式`

*
线程安全的单例模式实现-懒汉模式(只有真正的使用到getInstance的时候才真正的创建实例)
 */
class Singleton2{
    //1.和饿汉模式有所不同,此模式并不是立即初始实例
    private static volatile Singleton2  instance = null;
    //2.将构造方法设为private
    private Singleton2(){}
    //3.提供一个方法获取实例,但是只有真正需要的时候才会真正的去创造实例
    public static Singleton2 getInstance(){
                if(instance==null){
                    instance = new Singleton2();
                }
        return instance;
    }
}

和饿汉模式有所不同,此模式并不是立即初始实例,而是判断一下是否真的需要,只有在真正的需要的时候才会去创建实例

以上是懒汉模式的雏形,但是,我们随着深入了解,会发现,懒汉模式存在许多问题

if(instance==null){
     instance = new Singleton2();
   }

这两行代码即包含了读操作,也包含了修改操作,而且这两行代码是两个步骤,不是原子性的(何为原子性,可以去了解我之前写的线程安全问题的博客),也就是说此代码存在线程安全问题

线程安不安全,具体的多线程环境下,并发的调用getInstance,是否存在BUG

单例模式——饿汉模式和懒汉模式_第1张图片
如图解:我们不难看出,如果读操作和修改操作不是原子性的,就可能会导致多个线程创造出多个实例出来,就违背了我们的初衷,实现多线程的单例模式

所以,我们需要加锁操作:

synchronized(Singleton2.class){
                   if(instance==null){
                       instance = new Singleton2();
                   }
                   return instance;
               }
    }

上述代码使用了类对象作为了锁对象,类对象在程序中只存在一份,就能保证了多线程在调用getInstance的时候都是针对同一个对象进行的加锁

这样就保证了多操作和修改操作的原子性

加了锁之后线程变得安全了,但是又产生了新的问题

  1. 线程不安全是因为没有加锁,在加了锁之后,instance已经被初始化了,那么此时的instance就一定不是null了,这样代码就只会进行if和return的两个读操作,线程也就变的安全了,根据上述的代码,我么会发现,无论是在初始化前,还是在初始化之后,getinstance都是会被一直加锁的,如果instance已经被初始化了,那么在对getinstance进行加锁就会产生不必要的锁竞争

加锁虽然解决了线程安全问题,即开发效率,但是运行效率也随着降低,我们需要保证开发效率,也需要保证运行效率

改进方案:让instance初始化之前,才进行加锁,初始化之后就不需要加锁了,所以在加锁之前,加上一个判定条件,条件就是当前instance是否已经完成

 if(instance==null){
            synchronized(Singleton2.class){
                if(instance==null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
}

有细心的同学可能会发现,上述两个if的判断条件是相通的,那么将所有的代码放在一个if条件里不就好了吗?
这是绝对不可以的!!!
因为代码如果是这样的

 if(instance==null){
            synchronized(Singleton2.class){
                    instance = new Singleton2();
            }
        }
        return instance;

很明显,这样的代码没有保证读操作和修改操作的原子性,这也就代表着之前的所有铺垫都形同虚设了

那为什么会出现这种情况呢?这完全是一种美丽的巧合
1.第一个判定条件,是否需要加锁
2.第二个判定条件是否需要对instance初始化
这两个判定条件起到的效果/预期的目的是完全不一样的
碰巧这两个判定条件都是instance是否为null

在解决了上述代码后,还有最后一个重要的线程安全问题
我么发现,上述代码如果是多个线程共同调用这里的getinstance,因为这个方法只有一个(单例模式),就会导致方法会有大量的读操作产生,前面我们说到,如果代码有大量的读操作,编译器就会把这个读内存操作优化成读寄存器操作,这也就是我们所说的线程安全问题之一:内存可见性,这可能会引起第一个if判定条件产生误差,第二个不会产生影响,因为关键字synchronized,所以我们需要对instance这个变量进行修饰

  1. 使用volatile关键字

volatile可以保证变量不被编译器优化,但是不能保证原子性,此处我们只需要防止编译器优化即可

//1.和饿汉模式有所不同,此模式并不是立即初始实例
    private static volatile Singleton2  instance = null;
    //2.将构造方法设为private
    private Singleton2(){}
    //3.提供一个方法获取实例,但是只有真正需要的时候才会真正的去创造实例
    public static Singleton2 getInstance(){
        if(instance==null){
            synchronized(Singleton2.class){
                if(instance==null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
        }

懒汉模式总结

单例模式是我们秋招面试时非常重要的一个问题,懒汉模式尤为重要,懒汉模式产生的问题我们也需要熟练掌握

1. 在正确的位置加锁,保证读操作和修改操作的原子性
2. 双重if判定,避免产生不必要的锁竞争
3. 对instance使用关键字volatile,防止编译器优化

以上就是对线程安全中单例模式的总结,此案列非常重要,是多线程经典的案例,大家应该熟练掌握相关代码,最后可以写出来

你可能感兴趣的:(Java-EE,java-ee)