设计模式之单例模式——面试经典

单例模式

1、单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
2、要点:
①构造函数私有化
②private static修饰变量

3、单例模式的四种代码实现:
①饿汉式——线程安全
②懒汉式——线程不安全
③懒汉式——线程安全
④双重锁校验——线程安全

·

饿汉式——线程安全

不管需不需要用到该对象,都会先直接实例化该对象

  • 优点:不会产生线程不安全的问题
  • 缺点:如果一直用不到该对象,就会资源浪费
class Hungry{
    private Hungry(){}
    private static Hungry instance=new Hungry();
    public static Hungry getInstance(){
        return instance;
    }
}

·

懒汉式——线程不安全

只有用到该对象,才会去实例化该对象

  • 优点:instance的实例化延迟,如果用不到该对象,就不会去实例化该对象,节约资源
  • 缺点:线程不安全,多线程的情况下,会多次创建实例
class LazyNo{
    private LazyNo(){}
    private static LazyNo instance;
    public static LazyNo getInstance(){
       if (instance==null){
           instance=new LazyNo();
       }
       return instance;
    }
}

·

懒汉式——线程安全

跟线程不安全的懒汉式差不多,只是加了个锁

  • 优点:线程安全
  • 缺点:每次都要先获取锁,再去判断instance是否实例化了,效率过低。
    【如果instance已经实例化了,那其实没有必要先去获取锁,再判断,加锁解锁,很影响执行效率】
class Lazy{
    private Lazy(){}
    private static Lazy instance;
    public static synchronized Lazy getInstance(){
        if (instance==null){
            instance=new Lazy();
        }
        return instance;
    }
}

·

双重校验锁——线程安全

同时使用volatile和synchronized来保证线程安全和效率

  • 优点:线程安全,执行效率高
class Second{
    private Second(){}
    private static volatile Second instance;
    public static  Second getInstance(){
        //先判断instance是否实例化了,不为null,则直接返回instance
        if (instance==null){
           //获取锁之后再次判断instance是否实例化了
            synchronized(Second.class){
                if (instance==null){
                    instance=new Second();
                }
            }
        }
        return instance;
    }
}
为什么要两次if判断?

①第一个 if 判断是为了,在已经实例化过instance后,可以直接返回instance,不需要再去获取锁。【解决了 懒汉式——线程安全 的缺点】
②第二个 if 判断是为了避免在多线程情况下,多次实例化对象。
● 在多线程情况下,假设线程T1、T2、T3同时判断完了第一个 if,而T1先获取到了锁,T2、T3阻塞。
● T1在第二个 if 中判断instance为null后,T1便实例化instance
● 等待T1执行完毕,T2或T3获取到锁,通过第二个 if 判断得出结果,instance已经实例化了,所以T2或T3就不会实例化instance了,避免了多次实例化对象。
· · · 如果没有第二个 if 判断,T2或T3获取到锁后,就会直接实例化对象了。

·

为什么要用volatile修饰变量?

使⽤ volatile 可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏
Instance = new Singleton(); 这段代码其实是分为三步执⾏:
① 为 uniqueInstance 分配内存空间
② 初始化 uniqueInstance
③ 将 uniqueInstance 指向分配的内存地址
· 由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤geteInstance() 后发现 Instance 不为空,因此返回 Instance,但此时 Instance 还未被初始化。
·

·
【不知道我有没有讲清楚,欢迎评论或私信与我交流】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值