zoukankan      html  css  js  c++  java
  • ThreadLocal 原理解析

    1.对Thread local 理解

    ThreadLocal 是为了解决线程间同步而创建的一个新的思路。简单来说就是每个线程都保存一个变量副本。

    如果在Thread 内部定义一个field变量,也可以解决这个问题。

    这样就需要定义一个新的Thread类,来解决这个问题。每一次一个新的变量都需要这个case,but,实际这个新的类,与thread本身并没有关系。

    所以最好有一种方式,可以解决同步的问题,并且每个thread里面都有一份变量,但是不需要重新定义一个thread类,来集成这个功能。

    ThreadLocal就是这种思路。

    public final class Looper {
        /*
         * API Implementation Note:
         *
         * This class contains the code required to set up and manage an event loop
         * based on MessageQueue.  APIs that affect the state of the queue should be
         * defined on MessageQueue or Handler rather than on Looper itself.  For example,
         * idle handlers and sync barriers are defined on the queue whereas preparing the
         * thread, looping, and quitting are defined on the looper.
         */
    
        private static final String TAG = "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
    
        final MessageQueue mQueue;
        final Thread mThread;
    
        private Printer mLogging;
    
         /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          */
        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));
        }
    
        /**
         * Initialize the current thread as a looper, marking it as an
         * application's main looper. The main looper for your application
         * is created by the Android environment, so you should never need
         * to call this function yourself.  See also: {@link #prepare()}
         */
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    
        /**
         * Returns the application's main looper, which lives in the main thread of the application.
         */
        public static Looper getMainLooper() {
            synchronized (Looper.class) {
                return sMainLooper;
            }
        }
    
        /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }
    Looper

    Looper是android最核心的技术之一,消息机制。是整个UI层驱动的核心。它的思路如下,每个线程都可以有一个自己的消息队列。这个队列默认是没有创建的,(mainthread是系统创建的。)

    我们看到,这个类里面就有

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    也就是每个线程都有一个Looper对象。具体的细节不是本文的重点,可以看本博客的其他文章。

    2.ThreadLocal源码

    ...Androidsdksourcesandroid-23javalangThreadLocal.java

    最主要的几个函数,我们依次分析。

    public T get()
    protected T initialValue()
    public void set
    public void remove()

    先看get

    2.1 get

    public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null)
    return (T)e.value;
    }
    return setInitialValue();
    }

    既然是每个线程一个变量副本,那key作为Thread.currentThread()是最合适的。

    ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }

    ThreadLocalMap是个什么东西?

    这个是为了ThreadLocal使用,而创建的一种hashmap。

    它支持大数据量的使用,所以entry是使用weakreference的形式。所以把它作为HashMap来理解就可以了。

    剩下的代码,最难以理解的就是

    map.getEntry(this)

    为什么key是this,而不是currentThread。这个后面讲set的时候,可以在做分析。

    2.2 setInitialValue

    这个函数没有太多的花头,简单来说就是初始化。

    private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }

    先看map是否已经创建,如果有,设置初值,如果没有,先创建map,然后是设置初值。

     map.set(this, value);

     是的,又是这个this。this就是threadlocal这个类的实例,所以看到现在也没有发现,每个线程都有一份副本的代码。

    继续分析ThreadLocalMap

    继续看刚才的ThreadLocal的get & set,他们都在处理threadLocals,这个东西在哪里定义的,Thread。

    What? 这跟Thread有什么关系,ThreadLocal不是给每个线程都存一份副本吗,关Thread什么事情。

    回到第一章里里面的观点,Thread自己的local变量,才能做到没个实例都是单独的副本,不会存在冲突问题。

    /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;

    这段代码躺在Thread.java里面。

    也就是java编译器的作者,把需要添加的变量,放在了Thread里面。所以我们只要把我们的内容塞进这个map里面,就做到了每个thread都存在这样一个副本。

    如果类库把对于这个map的操作都封装了,我们只需要创建自己使用的变量就可以,yes。 这个事情ThreadLocal & ThreadLocalMap已经帮我们做了

    所以我们只要使用ThreadLocal就可以。我们继续分析,把各个细节都理清楚。

    3.ThreadLocalMap

    3.1 get & set

    继续看ThreadLocal的get & set,我们再把细节理一遍。

    ThreadLocalMap.Entry e = map.getEntry(this)
    private Entry getEntry(ThreadLocal key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }

     这个方法可以看成是hashmap的命中函数。先看hash表能否命中,没有,就全局扫描。

    所以简单来说,就是ThreadLocal就是从ThreadLocalMap(看成是hashmap)里面获取存储的值。key就是threadlocal这个类的实例。应为是线程唯一的。

     同理set也是相同的方法。

     3.2 entry

    static class Entry extends WeakReference<ThreadLocal> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal k, Object v) {
                    super(k);
                    value = v;
                }
            }

    WeakReference,使用弱引用的目的,就是app里面,或者说进程内所有的线程都共享这个threadLocals,所以内存可能会很大。这个在注释里面已经说的很清楚。

    3.3 table

     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);
            }

    可以看到table就是初始化的时候,获得的。ThreadLocalMap创建

    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }

    这个是ThreadLocal的代码,可以看到set里面有调用的代码。

    /**
             * The initial capacity -- MUST be a power of two.
             */
            private static final int INITIAL_CAPACITY = 16;

    在进行哈希值索引的时候,是需要

     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

    也就是说它是按位取&,所以i一定<= INITIAL_CAPACITY 。并且(INITIAL_CAPACITY - 1) 是“111111”这样的形式。

    
    
  • 相关阅读:
    【Uvalive4960】 Sensor network (苗条树,进化版)
    【UVA 1151】 Buy or Build (有某些特别的东东的最小生成树)
    【UVA 1395】 Slim Span (苗条树)
    【UVA 10600】 ACM Contest and Blackout(最小生成树和次小生成树)
    【UVA 10369】 Arctic Network (最小生成树)
    【UVA 10816】 Travel in Desert (最小瓶颈树+最短路)
    【UVA 11183】 Teen Girl Squad (定根MDST)
    【UVA 11865】 Stream My Contest (二分+MDST最小树形图)
    【UVA 11354】 Bond (最小瓶颈生成树、树上倍增)
    【LA 5713 】 Qin Shi Huang's National Road System (MST)
  • 原文地址:https://www.cnblogs.com/deman/p/7821495.html
Copyright © 2011-2022 走看看