不知不觉已经挖坑到设计模式中的单例模式,想必你看到了,其实老四不是按照顺序来写设计模式的,因为老四比较随性,遇到那个就研究哪个,然后看书敲代码并记录成文,但是标题一定还是按照正规顺序来写。还有就是:复制粘贴是最容易的编程,但也是最没有价值的编程。
大部分时候都把类的构造器定义成public访问权限,允许任何类自由创建该类的对象。但在某些时候,允许其他类自由创建该类的对象没有任何意义,还可能造成系统性能下降(因为频繁地创建对象、回收对象带来的系统开销问题)。例如,系统可能只有一个窗口管理器、一个打印机设备或者一个数据库引擎站点,此时如果在系统中为这些类创建多个对象就没有太大的实际意义。这个时候单例模式的作用就可大了呢。
定义
单例模式(Singleton):保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。
单例模式的动机:为了节约系统资源,确保系统只有这个类的一个实例就足够了,保证唯一性。
单例的实现思路:通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的方法就是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。我们都知道所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。所以在单例中我们需要将类的构造方法声明成private的形式。而且,在Java中,可以同过类成员的方式去声明对象实例,声明类方法,而类成员需要使用关键字static,所以单例模式因为Singleton类封装它的唯一实例,这样它可以严格地控制客户怎样访问它及何时访问它。
首先贴一下一个超级简单的单例代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package cpom.glorze.lowsingleton; /** * 最基本的单例代码实例类 * @ClassName Singleton * @author: glorze.com * @since: 2018年3月18日 下午4:57:38 */ public class Singleton { /** * 使用一个类变量来缓存曾经创建的实例 */ private static Singleton instance; /** * 对构造器使用private修饰,隐藏该构造器 * Singleton. * @descript 高老四博客 */ private Singleton() { } /** * 提供一个静态方法,用于返回Singleton实例 * 该方法可以加入自定义控制,保证只产生一个Singleton对象就好 * @Title: getInstance * @return Singleton * @author: 高老四博客 * @since: 2018年3月18日 下午4:58:46 */ public static Singleton getInstance() { if (null == instance) { instance = new Singleton(); } return instance; } public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton1 == singleton2); } } |
不出意外的话(根本就不会出现意外,日常客气),输出的结果是true,符合我们的要求。如果你是刚入门的话,其实你看到这里已经足够你理解单例这个思想以及基本实现了,但是基于精益求精(日常装逼模式开启),其实以上这段代码是非常low逼的代码。真正的单例代码还需要完善,他需要根据你系统的实际情况以及多线程等因素在上面的基础之上完美丰富一下代码。
首先介绍一下两个单例类型,分别是饿汉式单例和懒汉式单例。
- 饿汉式单例:顾名思义,就是它比较饥饿,在类初始化的时候你就杀愣的给人家的实例创建出来共别人使用。这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例。
- 懒汉式单例:顾名思义,就像你写代码的时候一定写过类似的”lazy=true”的代码,什么意思呢,就是这哥们比较懒,你让我干活,我才起床穿衣服给你干活,否则我猫着不出来活动。这种要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例。刚才那个老四写的上面的那个比较low的单例代码就是懒汉式单例。
接下来,我们看一下饿汉式单例是如何玩的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package cpom.glorze.hungrysingleton; /** * 饿汉式单例代码实例类 * @ClassName HungrySingle * @author: glorze.com * @since: 2018年3月18日 下午4:57:38 */ public class HungrySingle { /** * 使用一个类变量来缓存曾经创建的实例 * 类加载的时候就创建单例对象 */ private static final HungrySingle instance = new HungrySingle(); /** * 对构造器使用private修饰,隐藏该构造器 * HungrySingle. * @descript 高老四博客 */ private HungrySingle() { } /** * 提供一个静态方法,用于返回Singleton实例 * 该方法可以加入自定义控制,保证只产生一个Singleton对象就好 * @Title: getInstance * @return HungrySingle * @author: 高老四博客 * @since: 2018年3月18日 下午5:17:30 */ public static HungrySingle getInstance() { return instance; } public static void main(String[] args) { HungrySingle hungrySingle1 = HungrySingle.getInstance(); HungrySingle hungrySingle2 = HungrySingle.getInstance(); System.out.println(hungrySingle1 == hungrySingle2); } } |
懒汉式单例与饿汉式的优缺点对比:
- 饿汉式单例在类加载的时候就实例化,无须考虑多线程的问题,确保唯一性;
- 饿汉式单例一开始就被创建实例,所以调用速度和时间效率上是高于懒汉式单例的。
- 从资源利用的角度上来讲,无论系统用不用单例,饿汉式单例都创建实例了,会占用系统资源,系统加载时间变长。
========================丑逼分割线========================
- 懒汉式单例在第一次使用时才创建实例,无须一直占用系统资源
- 但是懒汉式单例必须考虑多线程并发问题,尤其系统初始化的时候涉及到并发访问的时候。
以上提及到懒汉与饿汉对比的时候,提到了懒汉式需要考虑多线程并发的问题,老四接下来给出单例的极致化代码,改代码解决了多线程模式的保证对象的唯一性。如果你正好在面试的时候碰到单例或者正在准备去面试,一定要记住下面的这段代码并理解其含义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package cpom.glorze.bestsingleton; /** * 最基本的单例代码实例类(懒汉式) * @ClassName BestSingleton * @author: glorze.com * @since: 2018年3月18日 下午4:57:38 */ public class BestSingleton { /** * 使用一个类变量来缓存曾经创建的实例 */ private volatile static BestSingleton instance; /** * 对构造器使用private修饰,隐藏该构造器 * BestSingleton. * @descript 高老四博客 */ private BestSingleton() { } /** * 提供一个静态方法,用于返回Singleton实例 * 该方法可以加入自定义控制,保证只产生一个Singleton对象就好 * 双重检查锁定来实现懒汉式单例类 * @Title: getInstance * @return BestSingleton * @author: 高老四博客 * @since: 2018年3月18日 下午5:39:03 */ public static BestSingleton getInstance() { // 第一重判断 if(null == instance) { // 锁定代码块 synchronized (BestSingleton.class) { // 第二重判断 if (null == instance) { instance = new BestSingleton(); } } } return instance; } public static void main(String[] args) { BestSingleton bestSingleton1 = BestSingleton.getInstance(); BestSingleton bestSingleton2 = BestSingleton.getInstance(); System.out.println(bestSingleton1 == bestSingleton2); } } |
针对以上的代码,老四解释几点内容:
为什么要使用双重检查判断instance?术语上叫做双重锁定(Double-Check Locking),试想一下,如果instance为null并且这个有两个线程在调用getInstance()方法,他们都通过了第一重检查,然后因为锁的原因只能进去一个线程,另外一个被阻塞。如果这个时候没有第二重检查,那么第一个线程创建了实例,他出去的时候第二个线程进来还能创建新的实例,单例变成双例,那不扯淡呢吗?所以双重锁定必须加。什么?你问我为什么要写两个instance判空?我不知道……..
为什么要使用volatile关键字声明instance,关于volatile关键字老四会在写一篇文章单独说明,这里简单说明一下:被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
你可能会问这还不是最完美的单例啊?我会告诉你的确是的!但是你去面试或者实际应用中以及足够了。但是为了极致化(刚才已经吹出一波极致化了),老四再给你介绍一种叫做按需初始化(Initialization on Demand Holder,简称IoDH)的技术,该技术解决了饿汉式单例不能实现延迟加载,解决了懒汉式单例因为线程安全控制带来的性能和代码繁琐的诟病!废话少说,上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package cpom.glorze.iodhsingleton; /** * 按需初始化单例 * @ClassName IodhSingleton * @author: glorze.com * @since: 2018年3月18日 下午5:55:34 */ public class IodhSingleton { /** * 对构造器使用private修饰,隐藏该构造器 * IodhSingleton. * @descript 高老四博客 */ private IodhSingleton() { } /** * 声明静态内部类在第一次调用的时候初始化instance * @ClassName HolderClass * @author: glorze.com * @since: 2018年3月18日 下午5:56:44 */ private static class HolderClass { private final static IodhSingleton INSTANCE = new IodhSingleton(); } /** * 提供一个静态方法,用于返回Singleton实例 * 该方法可以加入自定义控制,保证只产生一个Singleton对象就好 * 利用内部类返回实例 * @Title: getInstance * @return IodhSingleton * @author: 高老四博客 * @since: 2018年3月18日 下午5:58:10 */ public static IodhSingleton getInstance() { return HolderClass.INSTANCE; } public static void main(String[] args) { IodhSingleton iodhSingleton1 = IodhSingleton.getInstance(); IodhSingleton iodhSingleton2 = IodhSingleton.getInstance(); System.out.println(iodhSingleton1 == iodhSingleton2); } } |
关于以上这个极致化代码,由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。所以我们通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式,对,其实他才是最好的单例代码,哈哈。记住了吗,记不住那就理解他,就能写出来了。
关于懒汉式单例的极致化代码,其实是根据古人(怎么能是古人呢,应该是前辈)的经验一步一步优化出来的,老四没有把代码的演化过程贴出来,但是下面的源码工程包都写了代码的演化进程,告诉你是如何一步一步从最low的单例演化到带有双重检查的单例代码的。如有兴趣请下载源码工程包下载参考,里面也有相应的注释帮你理解。
源码工程包下载地址文末自助获取。
累死我了,更博不易,喜欢的老铁有能力烦请赞助盒烟钱,点我去赞助。抱拳。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。