Handler往MessageQueue中添加数据,消息队列是怎样变化的?如何保证线程安全的?

多个Handler往MessageQueue中添加数据,其内部是如何保证线程安全的?

Handler是一个线程间通信的机制,很多消息都会从子线程发送至主线程,而主线程只有一个Looper,发送的消息都被放置在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。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章