前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程安全和锁机制(四)谈谈 ThreadLocal 和 Handler

线程安全和锁机制(四)谈谈 ThreadLocal 和 Handler

作者头像
提莫队长
发布2021-03-03 14:31:04
3780
发布2021-03-03 14:31:04
举报
文章被收录于专栏:刘晓杰

一、ThreadLocal简介

ThreadLocal可以实现线程本地存储的功能。把共享数据的可见范围限制在同一个线程内,就无须同步也能保证线程间不出现数据争用的问题。 那么它是如何实现解决数据争用的问题呢。看代码

代码语言:javascript
复制
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

看代码就知道,通过 currentThread 获取 ThreadLocalMap。也就是说每个 Thread 都保存着 ThreadLocalMap。 接下来看看 ThreadLocalMap。它有个内部类 Entry。很明显,key就是ThreadLocal,value就是要保存的值。

代码语言:javascript
复制
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocalMap 内部就保存着这样的数组。同时注意是 WeakReference,不一定要等到 OOM 才会去回收 看一下 ThreadLocalMap 的构造函数,注意 table[i] 的算法(除留余数法?)

代码语言:javascript
复制
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        private static final int HASH_INCREMENT = 0x61c88647;
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }

再看一下resize

代码语言:javascript
复制
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;//很明显 扩容成原来的两倍
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);//重新计算下标
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

由此可见,ThreadLocal 就是为每个线程创建了一个数组table(key是ThreadLocal对应的hash值,val就是值) set的时候先根据Thread找到对应的table,然后在根据hash算法找到下标index,最终找到值。get同理

二、ThreadLocal在Android源码里的运用

很典型的就是常见的Handler和Looper

代码语言:javascript
复制
public final class Looper {
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    //通常我们会在子线程 Looper.prepare() .主线程早在创建的时候就已经有了
    public static void prepare() {
        prepare(true);
    }
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static void loop() {
        final Looper me = myLooper();//这里获取对应的loop。如果是子线程的looper,那么handleMsg最终一定在子线程
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ......
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        }
    }

    public static Looper myLooper() {
        return sThreadLocal.get();
    }
}

那么,接下来这段代码就很清晰了

代码语言:javascript
复制
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                // Handler 构造函数如果没有指明 Looper.getMainLooper(),那么默认是子线程的 Loop,下面的 is main thread  打印出来是 false
                handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        Log.e("handler", "is main thread = " + isMainThread() + ", what = " + msg.what);
                        return false;
                    }
                });
                handler.sendMessageDelayed(Message.obtain(handler, 1), 2000);
                Looper.loop();
            }
        }).start();

        findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.sendMessageDelayed(Message.obtain(handler, 2), 3000);
            }
        });
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、ThreadLocal简介
  • 二、ThreadLocal在Android源码里的运用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档