核心:使用共享对象可有效地支持大量的细粒度的对象。运用共享技术,使得一些细粒度的对象可以共享。
一、报名系统crash多台机器
报考系统crash,原因是使用了工厂模式来获取对象,在大访问量100万时,就会有100万个对象,因为JVM回收不及时,导致内存OutOfMemory,这里我们可以把对象获取换成一种"池"的形式
1.报考对象
public class SignInfo {
//报名人员的ID
private String id;
//考试地点
private String location;
//考试科目
private String subject;
//邮寄地址
private String postAddress;
get/set()
}2. 带对象池的报考信息
考试科目和考试地点是有限的,我们可以把它做为key,在池中生成有限的对象。
public class SignInfo4Pool extends SignInfo{
//定义一个对象池提取的KEY值
private String key;
//构造函数获得相同标志
public SignInfo4Pool(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}3. 带对象池的工厂类
public class SignInfoFactory {
//池容器
private static Map<String,SignInfo> pool = new HashMap<String,SignInfo>();
//报名信息的对象工厂
@Deprecated
public static SignInfo getSignInfo(){
return new SignInfo();
}
//从池中获得对象
public static SignInfo getSignInfo(String key){
//设置返回对象
SignInfo result = null;
//池中没有该对象,则建立,并放入池中
if(!pool.containsKey(key)){
System.out.println(key+"----建立对象,并放置到池中");
result = new SignInfo4Pool(key);
pool.put(key, result);
}else{
result = pool.get(key);
System.out.println(key+"----直接从池中取得");
}
return result;
}
}4. 场景类
public class Client {
public static void main(String[] args) {
//初始化对象池
for(int i = 0;i < 4;i++){
String subject = "科目"+i;
//初始化地址
for(int j = 0;j < 30;j++){
String key = subject + "考试地点"+j;
SignInfoFactory.getSignInfo(key);
}
}
//System.out.println(SignInfoFactory.pool.size());//120
SignInfo signInfo = SignInfoFactory.getSignInfo("科目1考试地点1");
}
}
==>
科目3考试地点24----建立对象,并放置到池中
科目3考试地点25----建立对象,并放置到池中
科目3考试地点26----建立对象,并放置到池中
科目3考试地点27----建立对象,并放置到池中
科目3考试地点28----建立对象,并放置到池中
科目3考试地点29----建立对象,并放置到池中
............
科目1考试地点1----直接从池中取得
二、享元模式的定义
享元模式是池技术的重要实现方式。
定义:使用共享对象可有效地支持大量的细粒度的对象。
享元模式中,要求细粒度对象,那么不可避免地使得对象数量多且性质相近,那我们就将这些对象的信息分为两个部分:内部状态(intrinsic)与外部状态(extrinsic)。
a. 内部状态
内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变,如我们例子中的id/postAddress等,它们可以作为一个对象的动态附加信息,不必直接储存在具体某个对象中,属于可以共享的部分。
b. 外部状态
外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态,如我们例子中的考试科目+考试地点复合字符串,它是一批对象的统一标识,是唯一的一个索引值。
1. 抽象享元角色
public abstract class Flyweight {
//内部状态
private String intrinsic;
//外部状态
protected final String Extrinsic;
//要求享元角色必须接受外部状态
public Flyweight(String extrinsic) {
Extrinsic = extrinsic;
}
//定义业务操作
public abstract void operate();
//内部状态的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}2. 具体享元角色
public class ConcreteFlyweight1 extends Flyweight{
//接受外部状态
public ConcreteFlyweight1(String extrinsic) {
super(extrinsic);
}
//根据外部状态进行逻辑处理
public void operate() {
//业务逻辑
}
}
&
public class ConcreteFlyweight2 extends Flyweight{
//接受外部状态
public ConcreteFlyweight2(String extrinsic) {
super(extrinsic);
}
//根据外部状态进行逻辑处理
public void operate() {
//业务逻辑
}
}3. 享元工厂
public class FlyweightFactory {
//定义一个池容器
private static Map<String,Flyweight> pool = new HashMap<String,Flyweight>();
//享元工厂
public static Flyweight getFlyweight(String extrinsic){
//需要返回的对象
Flyweight flyweight = null;
//在池中没有该对象
if(pool.containsKey(extrinsic)){
flyweight = pool.get(extrinsic);
}else{
//根据外部状态创建享元对象
flyweight = new ConcreteFlyweight1(extrinsic);
//放置到池中
pool.put(extrinsic, flyweight);
}
return flyweight;
}
}
三、享元模式的应用
1. 优点和缺点
降低内存的占用,但同时也提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
2. 使用场景
1) 系统中存在大量的相似的对象
2) 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
3) 需要缓冲池的场景
四、享元模式扩展
1. 线程安全问题
如果不和例子中一样使用“考试科目+考试地点”作为外部状态,而只使用“考试科目”或者“考试地点”作为外部状态呢,这样池中的对象会更少,运行可以是可以,但是池中设置的享元对象数量太少,导致每个线程都到对象池中获得对象,然后都去修改其属性,于是就出现一些不和谐的数据,所以在使用享元模式时,对象池中的享元对象尽量多,多到足够满足业务为止。
1)报考信息工厂
public class SignInfoFactory {
//池容器
private static Map<String,SignInfo> pool = new HashMap<String,SignInfo>();
//报名信息的对象工厂
@Deprecated
public static SignInfo getSignInfo(){
return new SignInfo();
}
//从池中获得对象
public static SignInfo getSignInfo(String key){
//设置返回对象
SignInfo result = null;
//池中没有该对象,则建立,并放入池中
if(!pool.containsKey(key)){
System.out.println(key+"----建立对象,并放置到池中");
result = new SignInfo();
// result = new SignInfo4Pool(key);
pool.put(key, result);
}else{
result = pool.get(key);
System.out.println(key+"----直接从池中取得");
}
return result;
}
}2)多线程场景
public class MultiThread extends Thread{
private SignInfo signInfo;
public MultiThread(SignInfo signInfo) {
this.signInfo = signInfo;
}
public void run() {
if(!signInfo.getId().equals(signInfo.getLocation())){
System.out.println("编号:"+signInfo.getId());
System.out.println("地址:"+signInfo.getLocation());
System.out.println("线程不安全了");
}
}
}3)场景类
public class Client {
public static void main(String[] args) {
//在对象池中初始化4个对象
SignInfoFactory.getSignInfo("科目1");
SignInfoFactory.getSignInfo("科目2");
SignInfoFactory.getSignInfo("科目3");
SignInfoFactory.getSignInfo("科目4");
//取得对象
SignInfo signInfo = SignInfoFactory.getSignInfo("科目2");
signInfo.setId("123");
SignInfo signInfo1 = SignInfoFactory.getSignInfo("科目2");
System.out.println(signInfo1.getId());
}
}2. 性能平衡
尽量使用Java基本类型作为外部状态(即HashMap中的Key),如果将例子中的外部状态“科目”和“考点”用类封装起来,这样似乎更符合面向对象,但因为是外部状态,是HashMap的key,所以要重写类中的equals和hashCode,这样才可以使用map的put或者get等,这样执行效率就会大大下降。
1)外部状态类
public class ExtrinsicState {
//考试科目
private String subject;
//考试地点
private String location;
get/set();
public boolean equals(Object obj) {
if(obj instanceof ExtrinsicState){
ExtrinsicState state = (ExtrinsicState) obj;
return state.getLocation().equals(location) && state.getSubject().equals(subject);
}
return false;
}
public int hashCode() {
return subject.hashCode() + location.hashCode();
}
}2)享元工厂
public class SignInfoFactory {
//池容器
private static Map<ExtrinsicState,SignInfo> pool = new HashMap<ExtrinsicState,SignInfo>();
//报名信息的对象工厂
@Deprecated
public static SignInfo getSignInfo(){
return new SignInfo();
}
//从池中获得对象
public static SignInfo getSignInfo(ExtrinsicState key){
//设置返回对象
SignInfo result = null;
//池中没有该对象,则建立,并放入池中
if(!pool.containsKey(key)){
System.out.println(key+"----建立对象,并放置到池中");
result = new SignInfo();
pool.put(key, result);
}else{
result = pool.get(key);
}
return result;
}
}3)场景类
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("上海");
//计算执行100万次需要的时间
long currentTime = System.currentTimeMillis();
for(int i = 0;i < 1000000;i++){
SignInfoFactory.getSignInfo(state2);
}
long tailTime = System.currentTimeMillis();
System.out.println(tailTime-currentTime);
}
}==》103ms
不使用外部状态类,用String类型代替
public class Client {
public static void main(String[] args) {
String key1 = "科目1上海";
String key2 = "科目1上海";
//初始化对象池
SignInfoFactory.getSignInfo(key1);
//计算执行10万次需要的时间
long currentTime = System.currentTimeMillis();
for(int i = 0;i < 1000000;i++){
SignInfoFactory.getSignInfo(key2);
}
long tailTime = System.currentTimeMillis();
System.out.println(tailTime-currentTime);
}
}
==》50ms
各位想想,使用自己编写的类作为外部状态,必须重写equals方法和hashCode方法,而且执行效率还比较低,这种吃力不讨好的事情最好别做,外部状态最好以Java的基本类型作为标志,如String、int等,可以大幅提升效率。
本文介绍享元模式在解决大量细粒度对象导致内存溢出问题中的应用,通过实例讲解如何利用对象池技术优化系统资源使用。
1345

被折叠的 条评论
为什么被折叠?



