设计模式之禅23
来源:互联网 发布:淘宝上怎么看淘宝达人 编辑:程序博客网 时间:2024/05/29 17:16
设计模式之禅23
真刀实枪之享元模式
- 从报考系统的内存溢出说起
- 内存溢出的两种可能
- 内存泄漏:无意识的代码缺陷,导致的内存泄漏,JVM不能获得连续的内存空间
- 对象太多:代码写的不严谨,产生了很多的对象,内存被耗尽
- 内存溢出的两种可能
- 熟悉一下报考系统的业务流程
- 没有账号先进行注册
- 登录
- 填写信息
- 考试科目
- 考试地点
- 准考证邮寄地址
- 。。。
- 确认提交
通过属性业务流程图,可以看出这个过程是非常简单的,那为什么还会导致内存溢出,出现宕机和崩溃?
- 先看看这个过程的类图
类图有了,因为框架需求,写了个工厂方法模式,下面来实现一下这个类图
SignInfo
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description 报考信息 */public class SignInfo { // 报考人员的id private String id; // 考试地点 private String location; // 考试科目 private String subect; // 邮寄地址 private String postAddress; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getSubect() { return subect; } public void setSubect(String subect) { this.subect = subect; } public String getPostAddress() { return postAddress; } public void setPostAddress(String postAddress) { this.postAddress = postAddress; }}
SignInfoFactory
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory { // 报名信息的对象工厂--简单演示(实际情况会比这个复杂) public static SignInfo getSignInfo() { return new SignInfo(); }}
Client
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client { public static void main(String[] args) { // 从工厂中获得一个对象 SignInfo signInfo = SignInfoFactory.getSignInfo(); // 进行其他业务处理 }}
- 先看看这个过程的类图
- 就是这么简单的代码,但是为什么还会产生内存溢出的问题,很标准的工厂方法模式呀!
- 看下错误分析报告是这么说的吧!
1. 内存突然从888MB飙升到1.6GB2. 新的对象申请不到内存空间,于是出现了OutOfMemoryError3. 报告列出宕机时刻内存中的对象,其中SignInfo对象就有400MB之多
- 这个就比较清晰了吧,内存中产生的对象还没有被回收又产生了一批对象,直到将内存全部用完,那该怎么办呢?
- 共享技术,类似于线程池和数据库连接池,这里咱们用的是对象池(Object Pool)
对象池
- 一个共享的对象池,需要实现两个功能
- 容器定义:容器可以容纳哪些对象
- 提供客户端访问的接口:池中有可用的对象时提供给客户端进行使用,否则建立新的对象,并放到对象池中
- 设计一下咱们的对象池
- 相同的属性提取出来
- 不同的属性在系统内进行赋值处理
类图设计
- 增加了一个子类,实现带缓冲池对象的建立,同时在工厂中加入了容器HashMap,保存所有的对象
代码
SignInfo4Pool【增加一个key值:一个key值唯一对应一个对象】
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfo4Pool extends SignInfo { // 定义一个对象的key值 private String key; // 构造函数获得相同的标志 public SignInfo4Pool(String key) { super(); this.key = key; } public String getKey() { return key; } public void setKey(String key) { this.key = key; }}
SignInfoFactory
package com.peng.xy;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory { // 池容器 private static HashMap<String, SignInfo> pool = new HashMap<String, SignInfo>(); // 从池中获取对象 public static SignInfo getSignInfo(String key) { // 设置返回对象 SignInfo result = null; // 池中没有该对象则建立,并放入其中 if (!pool.containsKey(key)) { // 建立对象,并放入对象池中 System.out.println("建立对象,并放入池中!"); result = new SignInfo4Pool(key); pool.put(key, result); } else { System.out.println("直接从池中获取对象~~"); result = pool.get(key); } return result; }}
SignInfo
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description 报考信息 */public class SignInfo { // 报考人员的id private String id; // 考试地点 private String location; // 考试科目 private String subect; // 邮寄地址 private String postAddress; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getSubect() { return subect; } public void setSubect(String subect) { this.subect = subect; } public String getPostAddress() { return postAddress; } public void setPostAddress(String postAddress) { this.postAddress = postAddress; }}
Client
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client { public static void main(String[] args) { String subject = "科目"; // 初始化对象池 for (int j = 0; j < 10; j++) { String key = subject + j + "考试地点" + j; SignInfoFactory.getSignInfo(key); } for (int i = 1; i <= 9; i += 2) { SignInfoFactory.getSignInfo("科目" + i + "考试地点" + i); } }}
执行结果
建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!直接从池中获取对象~~直接从池中获取对象~~直接从池中获取对象~~直接从池中获取对象~~直接从池中获取对象~~
享元模式的定义
- FLYweight Pattern
- 池技术的重要实现方式
- Use sharing to support large numbers of finegrained objects officiently.(使用共享对象可有效地支持大量的细粒度的对象)
- 共享对象
- 细粒度对象
- 内部状态:可共享的信息--不会随环境的改变而改变的信息
- 外部状态:依赖的标记,随环境的变化而变化
享元模式的通用类图
- 类图中的角色
- FLYweight:抽象享元角色【产品地抽象类:内部状态和外部状态】
- ConcreteFLYweight:具体享元角色【共享的享元角色】
- unharedConcreteFLYweight:具体的享元角色【不共享的享元角色】
- FLYweightFactory:享元工厂:职责非常简单,就是构造一个池容器,同时从容器中取出对象的方法
- 享元模式的关键在于共享技术,使得一些细粒度的对象 可以共享
通用代码展示
FLYweight
package xy2;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public abstract class Flyweight { // 内部状态 private String intrinsic; // 外部状态 private String extrinsic; // 要求享元元素角色必须接受外部状态 public Flyweight(String extrinsic) { super(); this.extrinsic = extrinsic; } // 定义业务操作 public abstract void operate(); // 获取内部状态 public String getIntrinsic() { return intrinsic; } // 设置内部状态 public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; }}
ConcreteFlyweight1
package xy2;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class ConcreteFLYweight1 extends Flyweight { // 接受外部状态 public ConcreteFLYweight1(String extrinsic) { super(extrinsic); } // 根据外部状态进行逻辑处理 public void operate() { // 业务逻辑 }}
ConcreteFlyweight2
package xy2;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class ConcreteFLYweight2 extends Flyweight { // 接受外部状态 public ConcreteFLYweight2(String extrinsic) { super(extrinsic); } // 根据外部状态进行逻辑处理 public void operate() { // 业务逻辑 }}
FlyweightFactory
package xy2;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class FLYweightFactory { // 定义一个池容器 private static HashMap<String, Flyweight> pool = new HashMap<String, Flyweight>(); // 享元工厂 public static Flyweight getFlyweight(String Extrinsic) { // 需要返回的对象 Flyweight flyweight = null; // 在池中没有该对象 if (!pool.containsKey(Extrinsic)) { // 创建对象 flyweight = new ConcreteFLYweight1(Extrinsic); // 加入池中 pool.put(Extrinsic, flyweight); } else { flyweight = pool.get(Extrinsic); } return flyweight; }}
享元模式的应用
- 享元模式的优点
- 简单--池技术
- 减少程序创建对象,共享某些数据
- 降低内存的占用
- 增强程序的性能
- 享元模式的缺点
- 增加了系统的复杂性
- 需要分离出内部和外部状态,而且外部状态有固化特性,不应该随着内部状态的改变而改变,否则导致逻辑混乱
享元模式的响应场景
- 系统中存在大量的相似的对象
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
- 需要缓冲池的场景
享元模式的扩展
线程安全的问题
例子
SignInfo
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description 报考信息 */public class SignInfo { // 报考人员的id private String id; // 考试地点 private String location; // 考试科目 private String subect; // 邮寄地址 private String postAddress; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getSubect() { return subect; } public void setSubect(String subect) { this.subect = subect; } public String getPostAddress() { return postAddress; } public void setPostAddress(String postAddress) { this.postAddress = postAddress; }}
SignInfoFactory
package com.peng.xy;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory { // 池容器 private static HashMap<String, SignInfo> pool = new HashMap<String, SignInfo>(); // 从池中获取对象 public static SignInfo getSignInfo(String key) { // 设置返回对象 SignInfo result = null; // 池中没有该对象则建立,并放入其中 if (!pool.containsKey(key)) { // 建立对象,并放入对象池中 System.out.println("建立对象,并放入池中!"); result = new SignInfo4Pool(key); pool.put(key, result); } else { System.out.println("直接从池中获取对象~~"); result = pool.get(key); } return result; }}
MultiThread
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class MultiThread extends Thread { private SignInfo signinfo; public MultiThread(SignInfo signinfo) { super(); this.signinfo = signinfo; } @Override public void run() { for (int i = 0; i < 10; i++) { i++; } if (!signinfo.getId().equals(signinfo.getLocation())) { System.out.println("编号:" + signinfo.getId()); System.out.println("考试地址" + signinfo.getLocation()); System.out.println("线程不安全!"); } }}
Client
package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client { public static void main(String[] args) { // 对象池中初始化四个对象 SignInfoFactory.getSignInfo("科目1"); SignInfoFactory.getSignInfo("科目2"); SignInfoFactory.getSignInfo("科目3"); SignInfoFactory.getSignInfo("科目4"); // 获得对象 SignInfo signinfo = SignInfoFactory.getSignInfo("科目2"); // 多线程执行 while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } signinfo.setId("aaa"); signinfo.setLocation("aaa"); new MultiThread(signinfo).start(); signinfo.setId("b"); new MultiThread(signinfo).start(); signinfo.setLocation("b"); new MultiThread(signinfo).start(); } }}
某次的执行结果
建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!直接从池中获取对象~~编号:b考试地址aaa线程不安全!
- 设置的享元元素数量太少,多个线程可能获得对象之后,去修改其属性值,于是就出现了一些不和谐的数据,只要使用java开发,多线程是不可避免的,那么该怎么避免这个问题呢?
性能平衡
尽量使用java的基本类型来作为外部状态
- 来看类图
代码
ExtrinsicState
package xy3;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class ExtrinsicState { // 考试科目 private String subject; // 考试地点 private String location; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } // 重写equals方法 @Override public boolean equals(Object obj) { if (obj instanceof ExtrinsicState) { ExtrinsicState state = (ExtrinsicState) obj; return state.getLocation().equals(location) && state.getSubject().equals(subject); } return false; } // 重写hashCode方法 @Override public int hashCode() { return subject.hashCode() + location.hashCode(); }}
SignInfo
package xy3;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfo { // 报考人员的id private String id; // 考试地点 private String location; // 考试科目 private String subect; // 邮寄地址 private String postAddress; // 外部状态 private ExtrinsicState es; public ExtrinsicState getEs() { return es; } public void setEs(ExtrinsicState es) { this.es = es; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getSubect() { return subect; } public void setSubect(String subect) { this.subect = subect; } public String getPostAddress() { return postAddress; } public void setPostAddress(String postAddress) { this.postAddress = postAddress; }}
SignInfoFactory
package xy3;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory { // 池容器 private static HashMap<ExtrinsicState, SignInfo> pool = new HashMap<ExtrinsicState, SignInfo>(); // 从池中获取对象 public static SignInfo getSignInfo(ExtrinsicState key) { // 设置返回对象 SignInfo result = null; // 池中没有该对象则建立,并放入其中 if (!pool.containsKey(key)) { // 建立对象,并放入对象池中 System.out.println("建立对象,并放入池中!"); result = new SignInfo(); pool.put(key, result); } else { result = pool.get(key); } return result; }}
Client
package xy3;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client { public static void main(String[] args) { // 初始化对象池 ExtrinsicState state1 = new ExtrinsicState(); state1.setSubject("科目1"); state1.setLocation("山西"); SignInfoFactory.getSignInfo(state1); ExtrinsicState state2 = new ExtrinsicState(); state2.setSubject("科目1"); state2.setLocation("山西"); long t1 = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { SignInfoFactory.getSignInfo(state1); } long t2 = System.currentTimeMillis(); System.out.println("取对象1000000次的时间:" + (t2 - t1) + "毫秒"); }}
- 执行结果【由于电脑的配置不同,所产生的时间不一定相同】
建立对象,并放入池中!取对象1000000次的时间:90毫秒
- 来看类图
最佳实践
- Flyweight:拳击比赛中的特轻量级比赛
- java API中的享元模式:String中的intern方法【如果是String的对象中有该类型的值,则直接返回对象池中的对象】
- 对象池与享元
- 对象池:着重在对象的复用上,池中的每个对象是可以替换的,从同一个池中获取的对象a和对象b是完全一样的,它解决的是复用问题
- 享元:着重共享,如何建立多个可共享的细粒度对象
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;
阅读全文
0 0
- 设计模式之禅23
- 23种设计模式彩图-设计模式之禅
- 设计模式之禅
- 设计模式之禅
- 设计模式之禅
- 设计模式之禅
- 设计模式之禅
- 设计模式之禅
- 设计模式之禅
- 设计模式之禅—23种设计模式详解_1 单例模式
- 设计模式之禅—23种设计模式详解_2 工厂模式
- 设计模式之禅—23种设计模式详解_3 抽象工厂模式
- [设计模式]<<设计模式之禅>>模板方法模式
- 《设计模式之禅》目录
- 《设计模式之禅》前言
- 《设计模式之禅》书评
- 《设计模式之禅》书评
- 初读《设计模式之禅》
- 类的作用域
- CentOS7 上搭建Git仓库服务
- Introducing Go.pdf 英文原版 免费下载
- 2016年蓝桥杯初赛-方格填数
- 服务和数据转移
- 设计模式之禅23
- eclipse中关于jdk版本的所有设置
- Core Java Volume I--Fundamentals, 10th Edition.pdf 英文原版 免费下载
- 快速消息队列
- CentOS7 安装Node.js
- 量产固态激光雷达 骑破车创业的光学博士这样被奥迪看上
- 摘番茄、戴VR,机器人在东京玩得很热闹!
- 新西兰推虚拟机器人公务员 沃尔玛要在你家装无人店
- 千万销量百亿市场 语音芯片崛起!