设计模式_单例模式
A.概述
1.概述
单例模式,是整个设计模式中最简单的,也是开发中最常用的
有些对象,我们只需要一个,比如:线程池,缓存,对话框等
如果被实例化多次,会导致程序出现很多问题
比如:程序行为异常,资源使用过量,或者结果不一致
2.与全局变量的区别
只创建一次对象,我们也可以直接在成员位置创建对象
也就是全局变量,我们也经常使用
但如果是全局变量,那么程序已开始运行,就要创建对象
如果在项目中,这个对象非常消耗资源,而这次执行中又没有使用,那不就很浪费资源
而单利模式不同,只有在使用时,才被调用,并且只创建一次
因此,单例模式确保一个类只有一个实例,并提供一个全局的访问点
3.单例模式分类
a.同步getInstance():单例模式之懒汉式
b.双重检查锁:单例模式之懒汉式
c.急切实例化:单例模式之饿汉式
4.下面就以一个巧克力工厂类来演示这三种模式
B.同步getInstance()
1.需求分析
巧克力的制作,需要计算机控制锅炉
将牛奶和巧克力融在一起,然后送到下一阶段
现在用代码模拟巧克力锅炉
package org.xxxx.oop.singleton; public class ChocolateBoiler { // 空的和煮过的 private boolean empty; private boolean boiled; // 代码开始,锅炉是空的,也没煮过 public ChocolateBoiler() { empty = true; boiled = false; } // 给锅炉添加原料 public void fill() { // 必须是空的才能添加 if (isEmpty()) { empty = false; // 锅炉满了 boiled = false; // 但还没煮 } } // 开始煮 public void boil() { // 判断必须是满的,并且煮了 if (!isEmpty() && isBoiled()) { boiled = true; // 煮了 } } // 排除 public void drain() { // 判断不为空,并且煮了 if (!isEmpty() && isBoiled()) { empty = true; // 排空 } } // 判断锅炉是否为空 public boolean isEmpty() { return empty; } // 判断是否煮过 public boolean isBoiled() { return boiled; } }
这就是一个完整的操作过程,看上去逻辑也很严谨
但我们想一下,如果存在两个实例,会发生什么样的事
一个锅炉只能是一个对象,因此,我们就要引入单例模式
2.初识单例模式
在每次创建对象时,判断是否存在,如果存在直接返回
只展示修改的代码,其他方法不变,参考上一段代码
package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局变量 private static ChocolateBoiler cb = null; // 私有构造,不能通过外部创建对象 private ChocolateBoiler() { } // 创建方法,实例化 public static ChocolateBoiler getInstance() { // 判断是否为空 if (cb == null) { cb = new ChocolateBoiler(); } // 返回该对象 return cb; } }
实例化问题解决了,我们再分析一下
一个工厂,不可能只有一个锅炉,会有很多
这么多锅炉也不是一个一个工作,肯定是同时工作
这就要考虑多线程,在【JavaSE学习笔记】多线程01这一部分中
引入了窗口卖票这一例子,多个窗口同时售票
会出现同一张票被多个窗口售出,余票为负数
线程不安全,为了使线程安全,因此就引入了同步机制
3.同步getInstance()代码
package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局变量 private static ChocolateBoiler cb = null; // 私有构造,不能通过外部创建对象 private ChocolateBoiler() { } // 创建方法,实例化 增加synchronized关键字 public static synchronized ChocolateBoiler getInstance() { // 判断是否为空 if (cb == null) { cb = new ChocolateBoiler(); } // 返回该对象 return cb; } }
通过增加synchronized关键字,迫使每个线程在进入这个方法之前
都必须等到别的线程离开这个方法,也就是说,两个线程不能同时出现在该方法中
4.缺点
这个方法比较简单有效,但同步机制可能会使程序执行效率下降100倍
如果使用频繁的话,就得重新考虑
同步锁会降低性能,当然对于不考虑性能的程序来说,无关紧要
C.双重检查锁
1.分析
对于同步Instance()来说,会降低程序的性能
我们考虑下,只有在线程第一次进入这个方法时才需要同步
也就是说,当对象被创建时,就不需要再去管同步了
2.volatile关键字
首先检查对象是否被实例化,如果没有,再进行同步
这就要引入volatile关键字,用来确保将变量的更新操作通知到其他线程
在两个或者更多的线程访问的成员变量上使用volatile
当要访问的变量已在synchronized代码块中,或者为常量时,不必使用
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的
因此不会将该变量上的操作与其他内存操作一起重排序
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方
因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞
因此volatile变量是一种比sychronized关键字更轻量级的同步机制
3.当一个变量被volatile定义的特性
a.保证此变量对所有的线程的可见性,这里的"可见性"
可见性:当一个线程修改了这个变量的值,volatile保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新
b.禁止指令重排序优化
4.双重检查锁代码
这样,当只有第一次创建对象时,才会进入同步机制package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局变量,用volatile修饰 private volatile static ChocolateBoiler cb = null; // 私有构造,不能通过外部创建对象 private ChocolateBoiler() { } // 创建方法,实例化 增加synchronized关键字 public static ChocolateBoiler getInstance() { // 先判断是否为空 if (cb == null) { // 对象不存在,开启同步锁 synchronized (ChocolateBoiler.class) { // 进入同步机制后,再次判断一下是否存在 if (cb == null) { // 不存在,创建对象 cb = new ChocolateBoiler(); } } } // 返回该对象 return cb; } }
因此,大大的减少了getInstance()的时间消耗
5.缺点
在JDK1.4以及更低的版本,本方法不适用
D.急切实例化
1.概述
上述的两种单例模式方法
同步机制:性能过低
双重检查锁:不适用于旧版本
在此介绍急切实例化,也就是饿汉式,开发中常用,也比较简单
2.急切实例化代码
如果程序中总是要创建并使用单利模式
或者在创建和运行时的负担不太繁重,可以使用该方法
保证了在任何线程访问静态变量之前,一定先被实例化package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局变量,直接创建对象,静态初始化器,只执行一次,保证了线程安全、对象唯一 private static ChocolateBoiler cb = new ChocolateBoiler(); // 私有构造,不能通过外部创建对象 private ChocolateBoiler() { } // 创建方法 public static ChocolateBoiler getInstance() { // 返回该对象,对象已经创建了,直接返回 return cb; } }