2022面试Android之ThreadLocal

主要功能:

针对一个对象,每个线程需要存储不同的值的时候,使用ThreadLocal会非常的方便。

图解:

针对一个全局变量local,线程1-4都持有local的引用。当线程分别对local进行修改后,其实修改作用域仅限当前线程可用。

public class ThreadLocalTest {
    //对thread1-4均可见的mThreadLocal,相当于上图中的String local变量
    private ThreadLocal<String> mThreadLocal = new ThreadLocal<String>() ;
    //设定一个锁,好对线程进行同步
    private final Object setLock = new Object() ;
    Thread t1 = new Thread(){
        public void run(){

            synchronized (setLock) {
                mThreadLocal.set("I'm t1");
                try {
                    setLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.d("gggl" , mThreadLocal.get()) ;
        }
    } ;

    Thread t2 = new Thread(){
        public void run(){
            synchronized (setLock) {
                mThreadLocal.set("I'm t2");
                try {
                    setLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.d("gggl" , mThreadLocal.get()) ;
        }
    } ;
    Thread t3 = new Thread(){
        public void run(){
            synchronized (setLock) {
                mThreadLocal.set("I'm t3");
                try {
                    setLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.d("gggl" , mThreadLocal.get()) ;
        }
    } ;
    Thread t4 = new Thread(){
        public void run(){
            synchronized (setLock) {
                mThreadLocal.set("I'm t4");
                try {
                    setLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.d("gggl" , "t4 " + this.getState()) ;
            Log.d("gggl" , mThreadLocal.get()) ;
        }
    } ;

    public void test(){
        mThreadLocal.set("I'm main");
        t1.start() ;
        t2.start() ;
        t3.start();
        t4.start();

        try {
            Thread.sleep(500) ;
            synchronized (setLock) {
                setLock.notifyAll();
            }


        } catch (Exception e) {
            e.printStackTrace();
        }



        Log.d("gggl" , mThreadLocal.get()) ;
    }
}
/**
 打印结果
 2022-09-15 14:46:09.873 12915-15252/com.example.interview D/gggl: I'm t1
 2022-09-15 14:46:09.873 12915-15253/com.example.interview D/gggl: I'm t2
 2022-09-15 14:46:09.873 12915-15254/com.example.interview D/gggl: I'm t3
 2022-09-15 14:46:09.873 12915-12915/com.example.interview D/gggl: I'm main
 2022-09-15 14:46:09.873 12915-15255/com.example.interview D/gggl: t4 RUNNABLE
 2022-09-15 14:46:09.873 12915-15255/com.example.interview D/gggl: I'm t4
* */

分析:

首先介绍几个相关类:Thread、ThreadLocal、ThreadLocalMap,这三个类的大致关于如下。

ThreadLocalMap是最后存储数据的类,为ThreadLocal的静态内部类,内部包含一个Entry[] table的数组。Entry的key是ThreadLocal,value为我们调用set方法的时候设置的值。

Thread持有ThreadLocal的引用,这表明每创建一个线程都会对应的创建出一个ThreadLocalMap(注:lazyload,因为并不是每个线程都有使用ThreadLocal的功能)。

ThreadLocal内部有两个常用方法set,get用于数据的存储。当调用set、get方法时第一句都是Thread.currentThread()首先获取当前线程的引用。所以后续的操作确保了作用域只在当前线程。当调用set方法时,最终调用的其实时 map.set(this, value),意为着向当前线程的成员变量MAP中存入了一个键值对,由于map为每个线程的成员变量,所以我们调用set存入的值得作用域也仅限当前线程。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
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();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Android Looper的线程唯一性

做过Android开发的同学应该都知道在android中只要线程调用了Looper.prepa()后就会在对应的线程创建一个looper,并且每个线程都只能有一个looper对象,每个线程的looper对象都不相同,正是使用了ThreadLocal达到的特性。

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

sThreadLocal是ThreadLocal的静态变量,说明所有的Thread共有sThreadLocal变量。在内存中的存放形式就相当于有一个Map存放了进程内的所有Looper。这个Map的key就是每个线程的ThreadLocal

猜你喜欢

转载自blog.csdn.net/mldxs/article/details/126873093