10-单例模式(饿汉式+懒汉式)+单例模式涉及的多线程问题

一、单例模式

1、设计模式:对问题行之有效的解决方式,是一种思想。有23种设计模式,复用性、灵活性、可维护性

2、学设计模式要明确该设计模式可以解决什么问题,明确后就可以知道该设计模式何时使用

3、单例设计模式:保证一个类在内存中的对象的唯一性,即一个类在内存中只能有一个对象

4、实现单例的步骤:

(1)私有化(private)该类的所有构造函数 -- 不允许其他程序用new创建该类对象

(2)通过new在本类中创建一个本类的对象(private)

(3)定义一个公有的方法将创建的对象返回 -- 对外提供一个方法(public)让其他程序可以获取该对象

5、单例的两种形式

(1)饿汉式:类一加载对象就已经存在了

class Single {
    //不把s设为public供外使用,是因为public的s不可控
    //s在getInstance()方法中被使用,而getInstance()是静态static的,所以,s也得是静态static的
    private static Single s = new Single();

    //私有化构造函数,不让外部创建该类对象
    private Single() {

    }

    /**
     * 对外提供获取该类对象的方法
     * 因为构造函数私有化,外部不能创建对象,就需要为外部提供获取对象的方法
     * 因为外部不能创建对象,所以,只能使用类名调用,所以方法是静态static的
     *
     * @return
     */
    public static Single getInstance() {
        //方法中可以给获取实例加入某些if条件进行限定,可控
        //static方法中的变量也只能是static的,所以,s也是static的
        return s;
    }
}

(2)懒汉式:类加载进来没有对象,只有调用了getInstance()方法,才会创建对象。这种方式也称为单例设计模式的延迟加载

class Single {
    private static Single s = null;

    private Single() {

    }

    public static Single getInstance() {
        if (s == null) {
            s = new Single();
        }
        return s;
    }
}

说明:

(1)延迟加载指的是懒汉式,面试时比较多见,但在真正开发时用饿汉式比较合适

(2)懒汉式看起来比较好,但先加载后加载,最终都要用到这个对象(用单例的目的就是为了获取对象),所以先加载后加载都可以。但懒汉式存在一些问题,如果懒汉式被多线程并发访问,存在着安全隐患,即保证不了对象的唯一性。如果要保证对象的唯一性,就要加锁,效率就降低了

二、单例模式涉及的多线程问题

1、懒汉式在多线程情况下,是否存在安全隐患?

      Single s是共享数据。如果getInstance()方法被run()方法调用,则多线程中操作共享数据的代码就有多条,存在安全隐患

处理方式:加同步

(1)使用同步函数

class Single {
    private static Single s = null;

    private Single() {

    }

    /**
     * 同步函数
     * 问题:
     * 一个线程创建了对象s,后面的线程只需要获取对象s即可
     * 但因为是同步函数,后面的线程在获取对象s前,依旧需要判断锁
     * 即 每次获取对象s都要判断锁,效率较低
     *
     * @return
     */
    public static synchronized Single getInstance() {
        if (s == null) {
            s = new Single();
        }
        return s;
    }
}

问题:多线程在访问时,第一次,线程创建对象s,之后的线程都是在获取对象s。但其余线程在获取对象s的时候,除了要判断s==null之外,还要多一次判断锁的操作。即 每次获取对象s都要判断锁,效率较低

解决:使用同步函数可以解决线程安全问题,但是却降低了效率。尝试使用同步代码块解决问题

(2)使用同步代码块

class Single {
    private static Single s = null;

    private Single() {

    }

    public static Single getInstance() {
        //判断 s==null 是必须的,在里面和在外面判断都一样
        //(判断锁是多做的一步,多了一次判断,消耗资源)
        if (s == null) {
            //同步代码块
            //getInstance()是静态static方法,没有this,也不能用 this.getClass()
            //所以,用 当前类名.class 做同步锁
            //-->  -->  会在此处因获取不到锁而等待
            synchronized (Single.class) {
                if (s == null) {
                    s = new Single();
                }
            }
        }
        return s;
    }
}

分析:

a)线程0:判断外层s==null,拿到锁,执行。后失去CPU的执行权,挂着(没有释放锁)

b)线程1:判断外层s==null,但没有锁,等待

c)线程0执行完,创建对象s。线程1拿到锁,进入,判断里层s==null,此时s!=null,不再创建

d)线程2:外层s!=null,进不去,不会再判断锁了。以后的线程也都不会再判断锁了

2、加锁是为了解决线程安全问题,多加一次 if(s == null) 判断是为了解决效率问题。可以用这种双重判断的形式来解决懒汉式的安全问题和效率问题

3、综上,写单例时,用饿汉式较好

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值