文章目录
1 、简介
(1)单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
(2)简而言之使用该模式设计的类,全局应保证只有一个实例。单例模式又称为单实例模式,它的写法很多,如:饿汉式、懒汉式、登记式等。
(3)通常单例模式在java语言中,有两种构建方式:
-
懒汉方式。指全局的单例实例在第一次被使用时构建。
-
饿汉方式。指全局的单例实例在类装载时构建。
2、单例模式的好处、使用场景
(1)单例模式的好处
它确保一个类在Java虚拟机里只有一个实例,使一个类只有一个对象,整个系统共享这个对象。也就是阻止new对象,共用一个实例化对象,这样可以有效的节省内存资源,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
(2)单例模式的使用场景
单例模式只允许创建一个对象,它节省内存资源,因此对象需要被公用的场合适合使用,如:
(1)需要频繁实例化然后销毁的对象。
(2)创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
(3)有状态的工具类对象。
(4)频繁访问数据库或文件的对象。
经典使用场景:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
3、饿汉式
在类创建的同时就已经创建好一个静态的对象供系统使用,对象不会再改变。
也就是在类创建的同时,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。
所以饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题。
(1)基本步骤
单例模式:当前类的实例,全局唯一
1>私有化构造方法( 阻止随便new )
2>定义一个公用的静态的方法,用于获取当前类型的实例
3>定义一个私有的静态的当前类型的实例, 供 步骤2的方法返回
(2)核心代码
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化singletonHungryType显然没有达到懒加载的效果。
4、懒汉式
方式一:懒汉式(线程不安全)
此种方式,没有考虑多线程访问,线程安全问题
方式二:懒汉式(线程安全)
如果是在getInstance2方法上加了一个synchronized,每次调用getInstance2()方法都要对对象上锁
那么我每次去执行getInstace2方法的时候都会受到同步锁的影响,
这样运行的效率会降低,其实只需要在第一次创建SingletonLazy02实例的时候加上同步锁就好了。
方式三:懒汉式(DCL双重锁定)
将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,
只有在singletonLazy为null,并创建对象的时候才需要加锁,性能有一定的提升。
也就是如上代码,只有在SingletonLazy03还没被初始化的时候才会进入到第16行,然后加上同步锁。
等SingletonLazy03一但初始化完成了,就再也走不到第16行了,
这样执行getInstance3方法也不会再受到同步锁的影响,效率上会有一定的提升。
这种方法叫做DCL双重锁定(Double-Check Locking)
方式四:懒汉式(静态内部类)
还是会有问题,所以有上面的写法。
例如:在Java指令中创建对象和赋值操作是分开进行的,也就是说INSTANCE = new SingletonLazy04();语句是分两步执行的。
但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,
然后直接赋值给INSTANCE成员,然后再去初始化这个Singleton实例,这样就可能出错了。
5、总结
单例模式理解起来简单,但是具体实现起来还是有一定的难度。选择用哪种形式的单例模式,取决于你的项目本身,是否有高并发的环境,还是需要控制单例对象的资源消耗。我们使用的时候,要做到具体情况具体分析,选择适合当前项目开发场景的单例模式。就目前来看,DCL和静态内部类单例模式是高并发场合首选的单例实现方式,在一些对并发要求不高的场合,我们也可以采用其他简单的写法,而不是一味地去追求高并发。