核心:使用共享对象可有效地支持大量的细粒度的对象。运用共享技术,使得一些细粒度的对象可以共享。
一、报名系统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等,可以大幅提升效率。