美文网首页Android技术进阶Android技术知识Android开发
Android开发Handler中加数据,内部是如何确保线程安全

Android开发Handler中加数据,内部是如何确保线程安全

作者: 谁动了我的代码 | 来源:发表于2022-08-29 21:43 被阅读0次

概述

Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

在Android开发中,经常会在子线程中进行一些耗时操作,等到操作完成后,就会用Handler将数据发送给主线程,通知主线程去做相应的操作。比如请求到网络数据之后更新UI等。子线程、Handler、主线程构成了一个消费者-生产者模式。生产者和消费者在同一时间段内共用同一个存储空间,生产者(子线程)向存储空间中添加数据(消息),消费者(主线程)向存储空间中取出数据(消息)。

解析:

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。

本章技术由bug樱樱提供,主要讲解Handler消息机制中的常见问题点;他在Android开发的面试中也是常见的。
————————————————
版权声明:本文为CSDN博主「bug樱樱」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_47933729/article/details/112640945

相关文章

网友评论

    本文标题:Android开发Handler中加数据,内部是如何确保线程安全

    本文链接:https://www.haomeiwen.com/subject/empknrtx.html