多线程代码设计模式之单例模式

目录

设计模式引入

饿汉模式

懒汉模式

单例模式总结


设计模式引入

1.1.什么是设计模式

(1)设计模式就是一种代码的套用模板。例如:一类题型的步骤分别有哪些,是可以直接套用的。

(2)像棋谱,也是一种设计模式。根据棋谱去下棋,棋术自然不会很差。

(3)官方定义就是说:设计模式就是一套经过反复使用、多人知晓、经过分类、代码设计经验的总结

(4)使用代码设计模式可以提高代码的下限。更容易被人理解,使用等等

本文我们主要介绍设计模式中的单例设计模式

1.2.单例设计模式

(1)单例模式,就是只有单个对象。

(2)单例模式,在整个代码进程中的某个类,有且只会产生一个对象,也不会多new出来前提的对象(这个类就是单例模式,它只会产生一个对象)

(3)如何保证只会有一个对象呢?那就需要程序员通过代码去设计,用代码去限制和规范。

(4)根本上可以保证对象是唯一的,这种设计模式就称为单例模式

(5)单例模式,本节内容介绍两种最常用的:饿汉模式和懒汉模式

饿汉模式

1.1.概念引入

(1)什么是饿汉模式?听名字就是一个很饿的汉子

(2)饿汉模式,就是在类加载的时候,就完成了对象的实例化

(3)我们把迫不及待的实例化对象这种行为,称为饿汉模式

1.2.代码设计

(1)类体

这个名字是可以随便起的 

class Singleton {
  
}

(2)实例化对象

这里直接实例化一个饿汉对象,赋值给instance引用。这个引用是private static类型的,外部访问不到,而且在类加载的时候,就把对象给初始化好了。 

class Singleton {
    private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
}

(3)提供获取对象的方法

class Singleton {
    private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
    //用于外部获取饿汉对象
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton() {
        //私有构造方法,外部无法再进实例化
    }
}

外部通过调用这个方法,就可以拿到对象的一个引用;多次调用改方法,获得的对象都是同一份。 

(4)私有构造方法

这是最关键的一步,将构造方法设置为私有的,外部就无法在new对象了。下面这也是“饿汉模式”的完整代码了

class Singleton {
    private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
    //用于外部获取饿汉对象
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton() {
        //私有构造方法,外部无法再进实例化
    }
}

(5)验证是否同一个对象

 public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }

我们发现,这两个对象都是一样的。

(6)验证外部不可new

以上就饿汉模式代码设计的全部了,还有一些小问题,下面接着介绍。

1.3.线程安全等问题

(1)线程安全问题

在多线程代码中,对于饿汉设计模式的代码,是线程安全的。

原因:饿汉模式在加载类的时候,对象就已经实例化好了,比线程通过get方法去获得改对象的引用更快;后续线程获取对象的时候,都是获取到的同一个变量。

(2)保证唯一对象问题

初心:类似饿汉模式的代码设计,都是提供给外部使用的,外部是无法new对象的;即使内部可以,但是设计者不会那么蠢。

外部new对象怎么办:外部是可以通过反射的方式再new一次对象,但是这种方式我们不考虑,反射本身的开销和成本也很大。就像有人下定决心要偷你家东西,你也无法防住。

懒汉模式

1.1.概念引入

(1)听名字,看似懒,其实是高效率

(2)懒汉模式:类很懒,当程序员需要对象而去调用时,它才会实例化对象

(3)我们把这种不着急实例化对象的,称为“懒汉模式”

1.2.代码设计 

(1)类体

老样子,名字随意 

class SingletonLazy {
  
}

(2)对象的引用

因为很懒,所以一开始是不实例化对象的 

class SingletonLazy {
    private static SingletonLazy instance = null;//一开始不初始化对象
   
}

(3)对外开放对象

只有在第一次调用改方法时,才会去实例化一个对象 

class SingletonLazy {
    private static SingletonLazy instance = null;//一开始不初始化对象
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
         if(instance == null) {
             instance = new SingletonLazy();
        }
        return instance;
    }

}

(4)关闭构造方法

只有这样,外部才没有办法去new对象 

class SingletonLazy {
   private static SingletonLazy instance = null;//一开始不初始化对象
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
         if(instance == null) {
             instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy() {

    }
}

上面就是一个和饿汉模式类似的代码结构了

(5)同一个对象

 public static void main(String[] args) {
        SingletonLazy s1  = SingletonLazy.getInstance();
        SingletonLazy s2  = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }

(6)外部不可new

这里打断一下,上面的代码并不完整,因为上面的代码是一个线程不安全的代码。

线程安全的完整代码:接下来介绍

class SingletonLazy {
    private static volatile SingletonLazy instance = null;//一开始不初始化对象
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() {

    }
}
1.3.线程安全问题

(1)为什么不安全?

在类加载完之后,并没有实例化出对象;此时两个线程去调用getInstance()方法,就很大可能会创造出两个对象,这就违背了一个对象原则了。

(2)为什么会有线程不安全问题?

我们从指令的执行和线程调度方面切入分析。下面都是按照发生线程安全问题的情况下

1)有两个线程t1、t2,先后调用了改方法,并且下面的条件都成立,并且进入

if(instance == null)

像这样先后进入if语句之后,他们就会创造出两个实例,这就是线程不安全的问题。

上面产生的问题就是由于操作不是原子性造成的,我们只需要进行加锁操作就好。 

1.4.设计线程安全代码

(1)加锁之后

private static Object locker = new Object();
public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
     synchronized (locker) {
         if(instance == null) {
              instance = new SingletonLazy();  
         }
     }
    return instance;
}

加锁之后,同一个时间就只会有一个线程进入if语句并且实例化对象,当该线程解锁之后,对象已经创造好了;此时第二个线程不再阻塞,并进入if语句,但是此时条件已经不成立便不会进入。此时,就不会由于多线程代码而产生问题了。

(2)判断是否要加锁

上面的代码产生线程安全问题的主要原因是:由于对象没有实例化好,但是,当对象实例化好之后再去调用该方法,是不会产生任何线程问题的。此时,锁就现得很笨重,因此,我们只需要让第一次调用时加锁就好,于是得出下面的改进代码

private static Object locker = new Object();
public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
}

这就是双重if判断,但是两个if语句发挥的作用不一样。

第一个if:判断是否要加锁

第二个if:判断是否要创建对象

(3)预防指令重排序

上述代码就是最终的安全代码了吗?不,还不是,还会存在一个问题,那就是指令重排序造成的线程安全问题。

instance = new SingletonLazy();

new对象这个代码大概可以分成三步

1.申请内存空间

2.调用构造方法(对内存空间进行初始化)

3.把此时内存空间的地址,赋值给instance引用

指令执行的顺序大概有两种:123或者132。在单线程中,两种顺序都不会出现问题,但是在多线程中,132的顺序是会出现问题的。

问题:当执行的顺序是132时,t1线程执行完3之后,也就是赋值给了instance引用,此时不为空,然后被调度走,t2线程就可以返回这个引用,此时这个对象内部是没有初始化的,里面的值都是0,t2线程拿着这个引用去做一些事情,就会引起一些问题。

所以,我们要加上volatile关键字。

private static volatile SingletonLazy instance = null;//一开始不初始化对象

可见,volatile关键字表面上是预防内存可见性问题,更深的是预防指令重排序问题。给变量加上,可以预防对该变量发生一下指令重排序,可以保证intance引用存放的是完整对象。

线程安全的懒汉模式代码:

class SingletonLazy {
    private static volatile SingletonLazy instance = null;//一开始不初始化对象
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
               }
            }
        }
        return instance;
    }
    private SingletonLazy() {
    }
}

单例模式总结

1.单例模式引用场景

(1)在实际的业务中,有的类,只需要有一个对象就够了

(2)比如,要写一个类,用来实现一些功能,可以加载上百G的数据。所以这个类只需要创造一个对象,就可以管理完这些数据了。如果是多个实例,消耗的内存也更加大。

2.懒汉模式的优势

(1)在程序加载中,如果有多个单例模式且是饿汉模式,那么在加载的时候就需要花费很多的时间。

(2)如果是懒汉模式,在程序加载的时候不会一下子创造出很多的对象,因此,时间效率就得到了提高。


  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小娥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值