一个男人的设计模式:单例模式

本文探讨了单例模式的概念,强调了构造器私有化和全局访问点的重要性。介绍了不同级别的单例模式实现,包括线程非安全、线程安全的初级和高级版本,特别是详述了“双重检查”方法的线程安全性,并讨论了volatile关键字的角色。最后提到了Spring框架中@Bean注解创建的单例对象的唯一性问题。
摘要由CSDN通过智能技术生成


在恋爱过程中,都希望自己能成为另一半的唯一,但是我们本身就是唯一的。正确的恋人的关系应该是“亲密有间”的,彼此都是独一无二的,无论是精神上,还是物质上。

1 我只出生一次

单例模式,顾名思义就是只能存在一个实例对象,对象的“出生”大多数情况通过new关键字来调用构造器方法来实现,为了保证其他对象不能通过new关键字来创建单例对象,需要将构造器方法私有化private。

2 只有监护人同意,才能一起玩

由于构造函数的私有化之后,为了获得单例对象,必须提供一个全局访问点(一个静态方法)

3 初级版本单例模式(线程非安全)

public class UniqeMan {
    //独一无二男人,单例对象,因为静态方法只能访问静态字段,所以加上了一个static,关键字
    private static UniqeMan uniqeMan;
    //构造函数私有化
    private UniqeMan() {}
    //必须通过监护人同意,才能和我一起玩  全局访问点
    public static UniqeMan getUniqeMan() {
        if (uniqeMan == null) {
        	//problem
            //我只能出生一次 只是在需要的时候进行实例化
            uniqeMan = new UniqeMan();
        }
        return uniqeMan;
    }
}

解释:饿汉式的单例模式,在需要的时候(uniqeMan == null)在实例化单例对象。
问题:,当两个线程同时运行到problem这个位置,很明显会创建两个UniqeMan对象,这两个线程获得的uniqeMan不是同一个实例对象。

4 初级版本单例模式(线程安全)

public class UniqeMan {
    //独一无二男人,单例对象,因为静态方法只能访问静态字段,所以加上了一个static,关键字
    private static UniqeMan uniqeMan;
    //构造函数私有化
    private UniqeMan() {
    }
    //必须通过监护人同意,才能和我一起玩  全局访问点
    public synchronized static UniqeMan getUniqeMan() {
        if (uniqeMan == null) {
            //我只能出生一次 只是在需要的时候进行实例化
            uniqeMan = new UniqeMan();
        }
        return uniqeMan;
    }
}

解释:在全局访问点上增加关键字synchronized ,保证该方法在多线程访问时某一时刻只有一个线程可以执行该方法。(静态方法是属于“类”,不属于某个实例,是所有对象实例所共享的方法。也就是说如果在静态方法上加入synchronized,那么它获取的就是这个类的锁,锁住的就是这个类。)
问题:当线程竞争激烈时,该方法保证的线程安全的效率较低。

5 中级版本单例模式(线程安全的骚操作)

public class UniqeMan {
    //独一无二男人,单例对象,
    private static UniqeMan uniqeMan = new UniqeMan();
    //构造函数私有化
    private UniqeMan() {
    }
    //必须通过监护人同意,才能和我一起玩  全局访问点
    public static UniqeMan getUniqeMan() {
        return uniqeMan;
    }
}

解释:private static UniqeMan uniqeMan = new UniqeMan();当类被加载到虚拟机中时,被static修饰的字段会进行初始化(new UniqeMan()),那么在使用全局访问点获得这个唯一对象的时候,该对象早就出生了。
问题:这个方法采用的是“饿汉式的单例模式”,通俗一点讲,就是不管你使不使用,我先把这个单例模式创建出来再说。极端一点讲,如果该对象一直不被使用,但是该对象会一直存在内存中。

5 高级版本单例模式(线程安全)

public class UniqeMan {
    //独一无二男人,单例对象,
    private static volatile UniqeMan uniqeMan;
    //构造函数私有化
    private UniqeMan() {
    }
    //必须通过监护人同意,才能和我一起玩  全局访问点
    public static UniqeMan getUniqeMan() {
        if (uniqeMan == null) {
            //problem
            synchronized (UniqeMan.class) {
                if (uniqeMan == null) {
                    uniqeMan = new UniqeMan();
                }
            }
        }
        return uniqeMan;
    }
}

解释:
使用synchronized来同步一个if判空语句的原因,假设没有第二层的判空操作。
当两个线程运行到problem位置时,一个线程获得了类的锁对象,执行了创建对象的操作,释放了类的锁对象;
第二个线程这时获得了锁对象,同样也会执行创建对象的操作,也会造成线程不安全的问题。
所以这里加了第二次的判空操作,这也叫“双重检测”方法。
单例对象uniqeMan新增的volatile 关键字,为什么需要增加关键字?
一旦一个共享变量(被多个线程访问的变量)被volatile修饰之后,那么就具备了两层语义:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2.禁止进行指令重排序。
这里主要是利用第一层语义,当多个线程运行到problem这里时,每个线程都会持有一个uniqeMan的副本对象(值为空),但是,当其中一个线程获得类锁并创建对象之后,其它线程并不知道该对象以及被修改了,此时通过volatile来修饰uniqeMan,使得新创建的uniqeMan变量不为空了,对其他线程来说是立即可见的。这样,当其它线程获得类锁后,进行第二次if判空时,就不会进入该语句中,保证了单例。
该方式同样是懒汉式的单例模式,但是是线程安全的。并且由于synchronized不是加在方法上的,所的作用范围小了许多,也就意味着每一个线程持有锁的时间会相对短一点,效率也会相对高一些。

自己的问题,待研究

  • Spring 的@Bean创建的对象默认也是单例的,那么它是如何保证对象的唯一性的呢?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值