设计模式轻松学之01 单例模式

前言

GOF( Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)联合出版的书中提到的设计模式一共有23种,可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。

单例模式的特点

这一章我们主要介绍创建型模式之单例模式(Singleton Pattern)。它的特点是,保证系统中某个类只有一个实例,并且提供一个全局访问点。在我们开发的系统中ServletContext和Spring框架的ApplicationContext都是单例的,数据库连接池也是单例的。

创建单例对象要保证两点:

1. 构造器私有化

这样其他人就无法通过new关键字来生成另一个对象。

2. 提供一个全局访问点

通常提供一个使用public static 来修饰的getInstance方法。

两种单例模式的特点

单例模式主要分为两种,恶汉式和懒汉式。

1. 恶汉式

恶汉式指的是类在被加载的时候就会生成实例,虽然可能会浪费一些空间,但是性能比较好,绝对的线程安全,而且写法简单,如果没有特殊要求的话推荐使用这种方法。

2.懒汉式

而懒汉式单例需要在调用的时候才创建,这种情况导致了可能会出现线程安全的危险,所以处理起来会比较的麻烦,而且效率也会受到影响。

恶汉式单例详解

第一种写法

恶汉式的写法非常简单:

public class HungrySingleton {
    // 构造器私有化
    private HungrySingleton(){}
    // 内部构造实例
    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
    // 全局访问点
    public HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
    // 其他业务代码...
}
第二种写法

还有一个变种的写法是将内部构造的实例放到静态代码块中来处理,但它们的原理是一样的:

public class HungrySingleton {
    // 内部构造实例
    private static final HungrySingleton HUNGRY_SINGLETON;
    static {
        HUNGRY_SINGLETON = new HungrySingleton();
    }
    // 构造器私有化
    private HungrySingleton(){}
    // 全局访问点
    public HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
    // 其他业务代码...
}

懒汉式单例详解

有问题的写法

首先我们用最简单的方式来写一个懒汉式单例:

public class LazySingleton {
    // 构造器私有化
    private LazySingleton(){}
    // 内部类实例
    private static LazySingleton lazySingleton;
    // 全局访问点
    public static LazySingleton getInstance() {
        // 如果内部类实例没有被初始化,那么进行初始化
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    // 其他业务代码...
}
使用synchronized关键字加锁

以上写法有一个问题,那就是如果多个线程同时进入if判断语句中,就会多次执行lazySingleton = new LazySingleton()这条语句。为了解决这个问题,需要在getInstance方法上加锁:

public class LazySingleton {
    // 构造器私有化
    private LazySingleton(){}
    // 内部类实例
    private static LazySingleton lazySingleton;
    // 全局访问点
    // **使用synchronized加锁**
    public synchronized static LazySingleton getInstance() {
        // 如果内部类实例没有被初始化,那么进行初始化
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    // 其他业务代码...
}
双重检查锁

但是,用 synchronized 加锁,在线程数量比较多情况下, CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。为了兼顾线程安全又提升程序性能,我们引入了双重检查锁的单例模式:

public class LazySingleton {
    // 构造器私有化
    private LazySingleton(){}
    // 内部类实例
    private static LazySingleton lazySingleton;
    // 全局访问点
    // **不在getInstance上加锁了**
    public  static LazySingleton getInstance() {
        // 判断内部类实例是否为空
        if (lazySingleton == null) {
            // 在if内部加锁
            synchronized (LazySingleton.class) {
                // 第二次判断是否为空
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
    // 其他业务代码...
}

双重检查锁也有可能会被阻塞,但是阻塞的地方是在getInstance方法的内部,而第一种锁阻塞的是整个类的阻塞,因此性能还是有不少的提升的。

内部静态类

那么单例模式还有没有更好的不用加锁的写法呢?还是有的,那就是使用内部静态类的方式:

public class LazyInnerClassSingleton {
    // 构造器私有化
    private LazyInnerClassSingleton(){}

    // 全局访问点
    public static final LazyInnerClassSingleton getInstance() {
        return InstantHolder.LAZY_INNER_CLASS_SINGLETON;
    }
    // 额外的内部类
    private static class InstantHolder{
        // 在内部类中初始化实例对象
        private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
    }

    // 其他业务代码...
}

单例模式小结

以上就是单例模式的全部内容,其实还算是比较简单的,因为它的功能其实也比较单一。我们在开发的时候通常会使用Spring IOC容器,通常可以通过配置来实现单例模式的目的。但是单例模式可以算是面试中比较多的一个问题,了解其中原理也有助于提高我们的核心竞争力。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值