单例模式介绍
在java编程中,某些情况下,某种实例我们需要保证它在进程中的唯一性,即只有一个该类的实例,这便是单例模式。既然只允许有一个实例,所以它的构造方法必须是私有的,否则随处都可以实例化该类,无法达到单例的目的,构造器私有,那么该单例对象的初始化必须在本类中,同时还需要通过通公共方法将实例暴露出去。
综上所述,得出单例模式3个特点:
- 构造方法私有
- 本类中初始化实例对象
- 存在暴露单例对象的公共方法
单例模式分类
那么,初始化实例对象是在什么时候呢?根据初始化时间的不同,我们一般将单例模式分为以下两类:
- 在类加载时就进行初始化,称为饿汉式单例,不存在线程同步问题
- 在首次外部获取单例对象,称为懒汉式单例,存在线程同步问题
饿汉式单例
1 | public class Singleton { |
饿汉式单例实现比较简单,在类加载时直接初始化,后续的获取单例对象不存在线程并发问题
懒汉式单例
先看下面这段代码:
1 | public class Singleton { |
这段代码,在多线程情况下,如果在注释处线程发生了切换,则单例对象会被多次实例化,无法实现单例的目的,涉及线程问题,我们一般会用synchronized加锁的方式处理
1 | public class Singleton { |
这段代码并不能解决线程并发问题,因为当多个线程都在执行过if判定,而在获取到锁之前发生线程切换时,单例对象还是会被多次实例化,无法实现单例模式,因此,我们需要在拿到锁之后再判定一次对象是否为空,正确代码如下所示:
1 | public class Singleton { |
有的同学可能会问,为什么不直接在方法上使用synchronized加锁,这样肯定能保证不会出现线程并发问题,原因是因为synchronized是一种比较重的锁,如果每次获取单例实例都要先拿锁的话,会使获取单例对象的方法编程串行执行,部分线程可能处于阻塞,没有并发执行效率高,当单例对象初始化之后,后续就不会出现获取锁的操作,执行性能也会高很多。
单例模式的应用场景
在某些业务场景下,如果需要保证某些资源的唯一性,例如全局统一配置,数据库连接池对象,全局缓存等场景,就可以使用到单例模式。