• 单例模式(饿汉式单例 VS 懒汉式单例)


    所谓的单例模式就是保证某个类在程序中只有一个对象

    一、如何控制只产生一个对象?

    1.构造方法私有化(保证对象的产生个数)

            创建类的对象,要通过构造方法产生对象

           构造方法若是public权限,对于类的外部,可以随意创建对象,无法控制对象个数

          构造方法私有化,这样类的外部就彻底无法产生对象,一个对象都没有。

    2.单例类的内部提供这个唯一的对象(static)

            构造方法私有化后,对于类的外部而言就一个对象都没有了。因此要在这个类的内部构造出这个唯一的对象,只调用一次构造方法即可(这个单例对象不能是类的成员属性,因为成员变量必须通过对象来访问,但是类的外部根本无法产生对象,(矛盾),因此这个对象必须使用static关键字修饰,静态变量,不依赖类的对象

    3.单例类提供返回这个唯一对象的静态方法供外部使用

    二、饿汉式单例

    饿汉式单例模式是天然的线程安全的。类加载时就创建了这个唯一的对象!!!

    1. /**
    2. * 饿汉式单例(类加载就产生这个唯一的对象,也不管外部是否调用该对象)。饥不择食,这个类一加载就把惟一的这个对象产生了,
    3. * 我也不管外部到底用不用这个对象,只要这个类加载到JVM,唯一对象就会产生
    4. **/
    5. public class SingleTon {
    6. // 惟一的这一个对象
    7. private static SingleTon singleTon = new SingleTon();
    8. private SingleTon() {}
    9. // 调用此方法时,singleTon对象已经产生过了,多线程场景下取回的是同一个单例对象
    10. public static SingleTon getSingleton() {
    11. return singleTon;
    12. }
    13. }

    三、懒汉式单例

    懒汉式单例:只有第一次调用getSingleTon(),表示外部需要获取这个单例对象时才产生对象

    1. public class LazySingleTon {
    2. private static LazySingleTon singleTon ;
    3. private LazySingleTon(){}
    4. public LazySingleTon getSingleTon(){
    5. if (singleTon == null){
    6. singleTon = new LazySingleTon();
    7. }
    8. return singleTon;
    9. }
    10. }

    多线程场景下会产生线程安全问题(不能确保只有一个对象产生) 

    在这个场景下,三个线程并发调用get方法,此时三个 线程看到的singleTon 都为null,因此,每个线程都创建了一个对象!!

    四、解决懒汉式单例的线程安全问题

    1.静态方法上加锁

    1. public synchronized static LazySingleTon getSingleTon(){
    2. if (singleTon == null){
    3. singleTon = new LazySingleTon();
    4. }
    5. return singleTon;
    6. }

    在方法上上锁,表示同一时间只有一个线程能进入此方法(其他线程想要进入此方法都等待获取锁成功的线程释放锁)。此时,getSingleTon()的内部都是单线程操作(锁的粒度太粗)。

    2.double-check(双重加锁)

    1. private volatile static LazySingleTon singleTon ;
    2. private LazySingleTon(){}
    3. public static LazySingleTon getSingleTon(){
    4. if (singleTon == null){
    5. synchronized (LazySingleTon.class){
    6. if (singleTon == null){
    7. singleTon = new LazySingleTon();
    8. }
    9. }
    10. }
    11. return singleTon;
    12. }

    volatile的作用:内存屏障,可见性

    此时有t1,t2,t3三个线程,t1首先获取到了锁,开始执行new操作,虽然还没完全结束,但此时的singleTon != null,对于刚开始执行代码的t2,t3来说,它们看到singleTon != null 直接返回了,但是返回后的单例对象是一个尚未完全初始化的对象

    此时采用volatile关键字修饰单例对象,new操作有着一堵墙,其它线程要能执行到return语句,JVM一定保证了new操作完全结束了,之后才会执行return语句

     double-check:防止其他线程恢复执行后多次创建单例对象

    当t1先进入同步代码块后,t2,t3卡在获取所得位置,

    t1产生对象后释放锁,

    t2,t3还是从获取锁的位置继续执行,在他们的工作内存中,singleTon == null 

    t2,t3就会再次new对象。

  • 相关阅读:
    【第十三章 节点流-字节型(FileInputStream,FileOutputStream)的使用,图片文件复制,指定路径下文件的复制】
    若依:用sqlite3随便掰饬掰饬
    MySQL高级篇知识点——索引优化与查询优化
    Autoware.AI 源码安装CPU版本
    【无标题】
    jQuery 效果- 动画
    交流访问 | 林乐博士探访日本互联网协会
    VR全景技术在教育中的应用:VR教学的“因材施教”
    【Hack The Box】windows练习-- acute
    Rest风格快速开发
  • 原文地址:https://blog.csdn.net/explorer363/article/details/132720181