22、享元模式—省内存、速度快的对象共享技术

享元模式(Flyweight):运用共享技术有效的支持大量细粒度的对象。

UML图:

22、享元模式—省内存、速度快的对象共享技术_第1张图片

package com.thpin.repository.designpattern;

import java.util.Hashtable;

public class FlyweightDemo {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight flyweight1 = factory.getFlyweight("flyweight1");
        Flyweight flyweight2 = factory.getFlyweight("flyweight2");
        Flyweight flyweight3 = factory.getFlyweight("flyweight3");
        Flyweight flyweight4 = factory.getFlyweight("flyweight4");
        Flyweight flyweight5 = factory.getFlyweight("flyweight4");
        flyweight1.operation("extrinsicState1");
        flyweight2.operation("extrinsicState2");
        flyweight3.operation("extrinsicState3");
        flyweight4.operation("extrinsicState4");
        flyweight5.operation("extrinsicState5");
        System.out.println(flyweight4 == flyweight5);
    }
}

/*
 * 享元接口
 */
abstract class Flyweight {
    public abstract void operation(String extrinsicState);
}

/*
 * 具体享元类
 */
final class ConcreteFlyweight extends Flyweight {
    private String internalState;

    public ConcreteFlyweight(String internalState) {
        this.internalState = internalState;
    }

    public void operation(String extrinsicState) {
        System.out.println("ConcreteFlyweight 内部状态:" + internalState + "  外部状态:" + extrinsicState);
    }
}

/*
 * 享元工厂
 */
class FlyweightFactory {
    private Hashtable flyweights = new Hashtable<>();

    public FlyweightFactory() {// 静态装载
        flyweights.put("flyweight1", new ConcreteFlyweight("flyweight1"));
        flyweights.put("flyweight2", new ConcreteFlyweight("flyweight2"));
        flyweights.put("flyweight3", new ConcreteFlyweight("flyweight3"));
    }

    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = flyweights.get(key);
        if (flyweight == null) {// 动态装载
            flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

结果:

ConcreteFlyweight 内部状态:flyweight1  外部状态:extrinsicState1
ConcreteFlyweight 内部状态:flyweight2  外部状态:extrinsicState2
ConcreteFlyweight 内部状态:flyweight3  外部状态:extrinsicState3
ConcreteFlyweight 内部状态:flyweight4  外部状态:extrinsicState4
ConcreteFlyweight 内部状态:flyweight4  外部状态:extrinsicState5

true

    享元模式可以避免大量相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度减少单个实例的数目。

    如果一个应用程序使用了大量的对象,而大量的对象造成了很大的存储开销时就应该考虑使用享元模式;还有就是对象的大多数状态可以转成外部状态(即用参数来替代属性),那么可以使用较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

    比如围棋游戏中的棋子,其实它们极其相似,棋子的颜色固定不变设置为内部状态,而棋子的位置是可变化的设置为棋子的方法参数,也就是外部状态。这样一来几百个棋子就可以用黑白两个棋子统统替代,不仅是一盘棋的棋子不用按个实例化,而且无论多少盘棋的棋子都可以使用享元模式提供的黑白两个棋子实例。试想一下如果这个游戏的用户有上百个,那么享元模式就为系统节省了上万个实例所占的系统开销,客户端越多越明显。但反过来想享元模式也是有缺点的,它的缺点就是内部状态属性外移成为方法的参数,参数多了,必然给客户端造成了使用上的困难,代码也变得复杂了,但这些都是值得的,因为它带来的性能的提升足以掩盖了这个缺点。

    学习享元模式的过程中一直有个想法在脑海里闪动,那就是缓存,redis、memcached等这类菲关系型缓存数据库,但从缓存数据以供读取的角度来考虑有时候没有享元模式来的更直接。为什么这样说,以Redis为例,一条或一组缓存数据往往要被成百上千的线程访问并实例化成java的对象或集合,那么就说明这个相同的数据被实例化了成百上千次,读取并实例化耗时实例化次数多又耗内存。而享元模式实例化一次对象,而数据也只有一份,读取操作是直接使用的jvm内存,省去了反序列化的过程,省时间,省空间。所以说享元模式但从获取数据上来说要优于redis。但是享元模式获取的实例往往是不能修改内部属性的,实例是共享的,如果一个线程想要给的对象的属性(状态)修改值,而其他线程想要给对象属性赋其他值,这时的享元模式是不适用的。享元模式做的是将不变的数据做内部属性,变得数据做方法参数,可变数据和行为关联获得不一样的结果。 单纯从保存数据上来看,把数据作为内部状态来保存,去除operation()方法和外部状态,享元模式就可以作为java自带的缓存工具来使用。

    举个例子,每个电商系统都有一个不易变的类目,这个类目往往是有后台系统维护的。最普遍的做法是将类目保存在关系型数据库中,同时在菲关系型缓存数据中做一个副本提供快速度读取(但还是比读堆内存或引用指向慢)。类目发生变化时修改数据库的同时更新缓存。类目既然不常变动,而又每时每刻被大量访问,如果把类目数据用享元模式来管理就可以省去反序列化操作,也不用被web浏览器或app大量用户请求时一遍一遍的重复实例化类目集合,这样既省时又节省内存,对系统性能会有一定提升。更直观一点,淘宝有一套相对复杂的类目体系,网上查了一下2017年淘宝双十一的数据,支付峰值:25.6万笔/秒,数据库处理峰值,4200万次/秒,那么不难预测类目被访问峰值:>100万次/秒,类目这个常被访问的数据肯定要超100万次每秒。那么就算获取类目这个接口只耗时10ms,那也说明至少有1万个线程持有类目集合,用享元模式至少可以省去淘宝系统一万个类目集合的内存,获取数据的速度至少也可以省1毫秒左右吧(反序列化很耗时的,曾经见到过Jackson反序列化10个对象用30ms吓死人,阿里的fastjson用时低于3ms)。这种大量并发读数据直接发送客户端显示而不修改的情况就可以用享元模式的处理方式得到优化。其实我并不知道淘宝是怎么处理类似这样的数据的。。。这个观点希望大牛予以指正。 很多池的的模型也是享元来实现的,字符串池、线程池、数据库连接池等。

你可能感兴趣的:(设计模式)