享元模式(Flyweight)



享元模式(Flyweight

 

此篇文档分成两部分,一、通过一个简单的案例实现去理解此模式的精髓;二、介绍使用了享元模式的Handler消息机制—Message

 

  • 概念:使用共享对象可以有效的支持大量的细粒度的对象

    (细粒度对象:将业务对象加以细分,从而得到更加科学合理的对象模型)

  • 使用场景:系统中存在大量的相似对象;需要缓冲池的场景

     

    享元模式图示.

     

    根据图示,下面我们通过一个简单的案例实现理解模式的精髓。

    一、实现场景描述:从深圳-海口的机票,肯定有N人都在向服务器发出请求,查询一些详细的信息,且对于每一个请求,服务器必须做出回答。假设当数以万人不间断地在请求数据时,如果每次都重新创建一个对象返回查询结果,那么必然会造成大量对象的创建、销毁,使得GC频繁操作,内存占用可想而知。下面通过享元模式实现,在用户查询时优先使用缓存的对象,没有缓存才创建。

  1. 抽象基类(Flyweight

    public interface ITickets {

        //FlyWeight,享元对象的抽象基类/接口

        public void showTickets(String bunk);

    }

     

  2. 实现类(ConcreateFlyweight

     

    public class F implements ITickets {   //具体的享元对象

        

        public String from; //始发地

        public String to;  //目的地

        public String bunk;//舱位类型

        public int price;   //价格

        F(String from ,String to){  //构造函数,主要就是初始化变量

            this.from=from;

            this.to = to;

        }       //享元对象中,可以共享的成为“内部状态”,反之,“外部状态”

        @Override

        public void showTickets(String bunk) {

            price= new Random().nextInt(10000);  //Note:价格是变化的,不能共享

            System.out.println("先生/女士,您好,您购买的从"+from +""+to+""+bunk+经出票成功!");"飞机票"+",价格:"+price+",

        }

    }

  3. 管理对象类

    public class FlyTicket {

     

    static Map<String ,ITickets> sTicketMp = new ConcurrentHashMap<String ,ITickets>();

     

        public static ITickets getTicket(String from ,String to){

            String key = from +"-"+to;

            if(sTicketMp.containsKey(key))  //如果容器中存在此对象,直接复用!

            {

                System.out.println("使用的是缓存对象:"+key);

                return sTicketMp.get(key);

            }

            else

            {

                System.out.println("创建对象:" +key );

                ITickets f = new F(from,to); //对象的创建

                sTicketMp.put(key,f);

                return f;

            }

            }

        }

    Note在享元模式中,它会建立一个Map对象容器(key-value,key是享元对象的内部状态,value是享元对象本身。这里通过ConcurrentHashMap实现,它在线程安全的基础上提供了更好的写并发能力(适合高并发的场景)。在这里,如果缓存的Map中存在对象就直接使用,否则会创建。避免了重复对象的存在。

     

  4. 测试类

    public class Test {

     

        public static void main(String args[]){

            ITickets t1 = FlyTicket.getTicket("深圳","海口");

            t1.showTickets("头等舱");

            ITickets t2 = FlyTicket.getTicket("深圳","海口");

            t2.showTickets("商务舱");

            ITickets t3 = FlyTicket.getTicket("深圳","海口");

            t3.showTickets("经济舱");

        }

    }

    Note:运行结果如下图所示

     

    从上图中也可以看出来它只有在第一次查询的时候创建了对象,之后类似的对象都是直接从Map容器中取出来。

     

  1. Android源码中的享元模式—Handler消息机制

     

     

    Handler工作原理示意图.

     

    由上述的示意图可知,Handler的工作原理大致为:(Android中不允许在UI线程/即主线程中更新UI界面。但是UI在其他的线程更新会抛出异常,这就产生了冲突!因此,一般只能在子线程完成UI更新的耗时工作之后,通过Handler对象将结果传递给UI线程进行更新。其实可以理解成:这是一个耗时的操作,不能在主线程完成!~)。

    注意这里的Handler只能在主线程中创建。因为如果在其他线程中创建Handler对象,就默认持有一个其他线程的Looper对象;其次,Handler又只能访问主线程中的Looper。因此会产生消息无法传递、获取的问题!

    MessageQueue消息队列是一个单链表的数据结构,不能被它的字面意思给误导了,它并不是一个队列。链表在数据的插入删除上的空间复杂度、时间复杂度相对于其他数据结构来说,略有优势。

    Looper轮训器,它会不断的从消息队列中不断“询问”,当有新的消息产生的时候,立马将新消息发送给Handler

    Thread将在子线程中处理完成的结果封装在Message对象,被上述的Looper检测到,由Handler发送到主线程。

     

    问题的引出:上述大致就是Handler的工作原理。我们很容易发现,在这过程中,消息对象Message的创建是可能会是最频繁的。那么在消息处理这一环节,源码是怎么实现的呢?下面开始我们一探究竟!(重点关注的是Message消息对象!

    在子线程中完成耗时的处理之后,我们一般这样封装结果:

    Message msg  = Message.obtain();

    msg.obj = obj  ;   //obj为结果的对象

    msg.what  = 1 ;   //结果对象的标识

    mHandler.sendmessage(msg);  //这里发送的方式也有多种

    在主线程中的handlerMessage()方法接收封装的结果:

    Object obj = msg.obj;

    If(msg.what == 1)

    {

        ..............do something to update UI ..............

    }

     

    1Message.obtain()

    从上述的例子中我们可以看出来,获取Message消息对象,并没有通过new,而是直接通过Message.obtain()的方式。源码如下所示:

     

    Path:Message.java/

     public static Message obtain() {

            synchronized (sPoolSync) {

                if (sPool != null) {   //如果不为空返回message对象

                    Message m = sPool;

                    sPool = m.next;

                    m.next = null;

                    m.flags = 0;  // clear in-use flag

                    sPoolSize--;

                    return m;

                }

            }

            return new Message();  //否则创建对象返回

    }

    Note: Message类实现Parcelable接口。熟悉的“Pool,池!(享元模式中的Map对象容器?)我也不确定,继续跟代码吧~

    sPoolSync是一个普通的对象 private static final Object sPoolSync = new Object();它在这里的作用就是当获取Message对象时进行同步锁。

    private static Message sPool;从它的声明中可以看出来sPool是一个Message对象。

     

    // sometimes we store linked lists of these things

     Message next; //从上述的注释中也可以看出来,我们常说的MessageQueue使用的是list数据结构。这里的next相当于链表中的指针域,指向下一个Message。因此,所有可用的Message对象就是通过next串连成一个可用的Message池。

    综上所述,obtain()就是通过next获取,存储对象。使用了list数据结构。且从代码的整体来猜测,当“池”中不为空的时候,即存在该对象的时候直接返回,否则会创建。这似乎看得有点像享元模式中防止创建过多对象的理念了。那么pool池中又是如何去判断消息存在与否呢?继续看那下面的源码.......

    2recycle( )

    当我们创建消息的时候,它不会把Message对象放到Pool池中,在回收(并不是GC操作)该对象时才将对象添加到list中。

     public void recycle() {

            if (isInUse()) {

                if (gCheckRecycle) {

      throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");

                }

                return;   //判断是否在使用

            }

            recycleUnchecked();

        }

    Note:如果消息对象在使用,会抛出IllegalStateException异常;否则,调用recycleUnchecked()函数。

     

  1. recycleUnchecked()

        void recycleUnchecked() {

            flags = FLAG_IN_USE; //标记是否使用过

            what = 0;

            arg1 = 0;

            arg2 = 0;

            obj = null;

            replyTo = null;

            sendingUid = -1;

            when = 0;

            target = null;

            callback = null;

            data = null;

            synchronized (sPoolSync) {

                if (sPoolSize < MAX_POOL_SIZE) {  //MAX_POOL_SIZE = 50

                    next = sPool;

                    sPool = this;

                    sPoolSize++;

                }

            }

    Note: 上述函数会将Message消息回收到“池”中,即链表list中。且还会进行判断标记是否使用过该消息,新消息会被插到链表的表头, sPoolSize++;当然,如果链表中已经存在此消息对象的时候,直接复用,  sPoolSize--,返回给了调用obtain函数的客户端程序。

     

    链表中消息结构示意图:

     

     

     

     

     

     

    总结:这里的Message消息对象管理虽然并不能一一对应上享元模式中的抽象基类、基类的实现类、对象的管理类。但整体说来,Message可以说是采用了享元模式的精髓!因为,Message通过在内部构建一个链表来维护一个被回收(已经使用过的消息,并不是GC操作)的Message对象的对象池。当用户调用obtain()的时候,会优先从池中取,如果没有则创建(其实也有点类似于Activity启动模式中的栈内复用模式—singleTask思想!)。新创建的对象在被使用完之后会被回收到池中,以备下次复用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值