Handler消息机制(五):多个Handler往MessageQueue中添加数据,内部是如何确保线程安全的

Handler是一个线程间通信的机制,很多消息都会从子线程发送至主线程,而主线程只有一个Looper,发送的消息都被放置在MessageQueue这个队列中来,如何保证队列的混乱(如何保证线程安全)?

Handler消息机制(五):多个Handler往MessageQueue中添加数据,内部是如何确保线程安全的

看入队列的方法enqueueMessage:

    boolean enqueueMessage(Message msg, long when) {



        if (msg.target == null) {



            throw new IllegalArgumentException("Message must have a target.");



        }



        if (msg.isInUse()) {



            throw new IllegalStateException(msg + " This message is already in use.");



        }



 



        synchronized (this) {



 



          ......



 



        }



        return true;



    }

很清楚的看到这个方法里面有锁(synchronized),既然入队列里有锁,那再看看取消息是不是也有锁?

    Message next() {



 



        ......



 



            synchronized (this) {



               ......



            }



 



        ......



    }

是的,也是存在锁的。

所以,它是通过synchronized来保证了线程的安全性。

Handler所发送的Delayed消息时间准确吗?

实际上,这个问题与线程安全性为同一个问题,多线程中线程一旦安全,时间就不能准确;时间一旦准确,线程就一定不安全。

所以,Handler所发送的Delayed消息时间基本准确,但不完全准确。

因为多个线程去访问这个队列的时候,在放入对列和取出消息的时候都会加锁,当第一个线程还没有访问完成的时候,第二个线程就无法使用,所以他实际的时间会被延迟。

我们在使用Message的时候应该怎样创建它?

由于Message创建非常频繁,如果不断以new的方式去创建它,它的内存抖动是很恐怖的。

所以在Android的Message机制里面,对Message的管理采用了享元设计模式

先来查看Message.obtain()都有哪些操作?

    /**



     * Return a new Message instance from the global pool. Allows us to



     * avoid allocating new objects in many cases.



     */



    public static Message obtain() {



        synchronized (sPoolSync) {



            if (sPool != null) {



                Message m = sPool;



                sPool = m.next;



                m.next = null;



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



                sPoolSize--;



                return m;



            }



        }



        return new Message();



    }

obtain()维持了一个Message的pool(池子)。

    private static Message sPool;

我们在构建一个消息的时候,一般的步骤是先obtain一个消息,然后对它的各个字段进行设置,像target、data、when、flags…

当MessageQueuez去释放消息的时候(quit),它只是把消息的内容置空了,然后再把这条处理的消息放到池子里面来,让池子不断变大。

    /**



     * Recycles a Message that may be in-use.



     * Used internally by the MessageQueue and Looper when disposing of queued Messages.



     */



    void recycleUnchecked() {



        // Mark the message as in use while it remains in the recycled object pool.



        // Clear out all other details.



        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) {



                next = sPool;



                sPool = this;



                sPoolSize++;



            }



        }



    }

在这个池子里面,最多放置50个消息。

    private static final int MAX_POOL_SIZE = 50;

如果消息超过了50个消息,这个池子也不要了,然后mMessage也为空,则它也会被及时的回收。

    private void removeAllMessagesLocked() {



        Message p = mMessages;



        while (p != null) {



            Message n = p.next;



            p.recycleUnchecked();



            p = n;



        }



        mMessages = null;



    }

使用Handler的postDelay后消息队列将会有怎样的变化?

我们从postDelay的方法来开始追:

    public final boolean sendMessageDelayed(Message msg, long delayMillis)



    {



        if (delayMillis < 0) {



            delayMillis = 0;



        }



        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);



    }

这时候就是给消息队列添加一个消息时刻,如果这个消息队列为空,这个消息就不会被执行,只有一个消息,这个消息就不会被发送,而是计算等待的时间。

在添加消息的时候,在MessageQueue的时候,他有一个计算,MessageQueue里面有一个enqueueMessage()。在这个enqueueMessage中,一旦添加了消息之后,他就执行nativeWake()唤醒消息队列,这个消息队列就醒来。

            if (needWake) {



                nativeWake(mPtr);



            }

这个消息队列醒来之后,在MessageQueue里的next()函数就会触发关于要等待多长时间的计算。

                	//开机到现在的毫秒数如果小于msg.when则代表还未到发送消息的时间



                    if (now < msg.when) {



                        // Next message is not ready.  Set a timeout to wake up when it is ready.



                        // 虽然有消息,但是还没有到运行的时候



                        //计算还有等待多久,并赋值给nextPollTimeoutMillis



                    	//设置下一次查询消息需要等待的时长



                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);



                    }

计算完这个等待时间之后,这个for循环结束,结束完之后回过头来,就再跑回这里再次睡眠。

            //native的方法,在没有消息的时候回阻塞管道读取端,只有nativePollOnce返回之后才能往下执行



            //阻塞操作,等待nextPollTimeoutMillis时长



            nativePollOnce(ptr, nextPollTimeoutMillis);

所以说,他会先计算需要等待的时间,计算完需要等待的时间之后,就会进行对应的操作,然后重新让这个消息进行wait。

上一篇:看完吊打面试官!吃透这份Android高级工程师面试497题解析,面试真题解析


下一篇:没想到一个Handler还有中高级几种问法,附面试题答案