【JavaEE学习笔记】设计模式_单例模式

来源:互联网 发布:php addoption 编辑:程序博客网 时间:2024/05/22 00:03

设计模式_单例模式

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;}}
保证了在任何线程访问静态变量之前,一定先被实例化


原创粉丝点击