编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1App启动流程梳理
  • 1.2ActivityThread分析
  • 1.3Context设计思想
  • 2.1Activity基础介绍
  • 2.2Activity启动流程
  • 2.3Activity布局创建
  • 2.4Activity布局绘制
  • 2.5Service基础介绍
  • 2.6Service启动流程
  • 2.7Receiver广播基础
  • 2.8Receiver深入原理
  • 2.9ContentProvider分析
  • 2.10Fragment实践
  • 2.11Intent深入思考
  • 3.1Paint和Canvas
  • 3.2View的绘制基础
  • 3.3onMeasure流程设计
  • 3.4onLayout流程设计
  • 3.5onDraw流程设计
  • 3.6View工作原理
  • 3.7View刷新设计流程
  • 3.8自定义View控件
  • 3.9自定义ViewGroup控件
  • 4.1Handler基础使用
  • 4.2消息机制流程分析
  • 4.3Handler深度解析
  • 4.4Message深度理解
  • 4.5MessageQueue解析
  • 4.6Looper深度解析
  • 4.7理解Handler同步屏障
  • 4.8ThreadLocal分析
  • 4.9ThreadLocal场景
  • 5.1View事件设计思考
  • 5.2View滑动冲突处理
  • 5.3View事件源码分析
  • 5.4View事件总结案例
  • 6.1DecorView设计思想
  • 6.2视图的载体View
  • 6.3视图管理者Window
  • 6.4窗口管理服务WMS
  • 6.5布局解析者Inflater
  • 7.1AsyncTask深入介绍
  • 7.2HandlerThread设计
  • 7.3IntentService设计
  • 8.1IPC通信方式介绍
  • 8.2AIDL进程间通信
  • 8.7Binder通信机制设计
  • /zh/android/basic/9.1注解设计思想和原理.html
  • 9.2APT技术设计详解
  • 9.3APT多种案例实践

4.8ThreadLocal分析

目录介绍

  • 01.Android消息机制
  • 02.ThreadLocal基础介绍
  • 03.ThreadLocal分析
    • 3.1 set()方法
    • 3.2 get()方法
  • 04.Handler为何用ThreadLocal

01.Android消息机制

  • 说起Android消息机制,老生常谈的问题,Handler,Looper,MessageQueue,Message这几个都是离不开的话题,不同的类承载不同的功能,首先还是简单的总结下各自的功能:
    • Handler:发送和处理消息
    • MessageQueue:消息队列,采用单链表结构存储数据
    • Looper:调用loop()轮询消息
    • Message:需要发送的内容,消息

02.ThreadLocal基础介绍

  • 定义:早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。
  • ThreadLocal的作用主要是用来做什么的?
    • 这个类提供线程本地变量。这个变量不同于线程中普通的副本变量,因为每个线程都持有一个属于它自己的变量,并且可以通过 get、set 方法来对其进行访问和修改,并且初始时会独立创建变量的副本保存在每个线程中。ThreadLocal 对象一般是一个线程的私有字段,用于和线程中的某个信息相关联(比如:用户 ID、交易 ID)。
    • 每个线程都拥有对其保存的线程局部变量副本的隐式引用,只要线程处于活动状态并且其对应的 ThreadLocal 对象字段可用时就可以访问。线程结束后,所有的线程保存的 ThreadLocal 对象将会被垃圾回收器回收,除非还有其他的引用指向这些存在的对象。
    • 从官方说明中,我们可以知道利用这个类我们可以在每一个线程中都保存一个变量,并且不同线程中的这个变量互不冲突,我们还可以通过 ThreadLocal 对象的 get、set 方法来读取、修改这个变量的值。
  • 使用这个工具类可以很简洁地编写出优美的多线程程序,先上一段代码瞧瞧:
    private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
    
    button.setOnClickListener(new View.OnClickListener() { 
        @Override 
        public void onClick(View v) { 
            booleanThreadLocal.set(true); 
            booleanThreadLocal.set(false); 
            Log.i(TAG, "run: ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get()); 
            new Thread("Thread1"){ 
                @Override 
                public void run() { 
                    booleanThreadLocal.set(true); 
                    Log.i(TAG, "run: Thread1+ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get()); 
                } 
            }.start(); 
            new Thread("Thread2"){ 
                @Override 
                public void run() { 
                    Log.i(TAG, "run: Thread2+ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get()); 
                } 
            }.start(); 
        } 
    });
  • 代码很简单就是给一个button设置一个点击监听事件,然后通过获取ThreadLocal的get方法分别获取里面值:我们来看打印值(界面过于简单,就不在展示):
    I/MainActivity: run: ThreadName-------->main---->false 
    I/MainActivity: run: Thread2+ThreadName-------->Thread2---->null 
    I/MainActivity: run: Thread1+ThreadName-------->Thread1---->true
  • 在不同的线程采用相同的对象调用他们的get方法,但是他们打印的值却是不一样,很奇妙吧,这也就是他的奇妙之处。

03.ThreadLocal分析

  • 看了上面的案例结果。首先简答的介绍为什么会产生这种效果,方便后边更好的看代码。
    • ThreadLocal 内部维护了一个针对每一个线程的数组 Entry[],它的初始容量是16,我们在设置 value 的时候将当前的 value 值封装 Entry 类里面,在然后再根据当前的 ThreadLocal 的索引去查中对应的 Entry值,最终根据Entry对象取出value值,很明显每个线程的数组是不相同,所以就可以取出不同的 Entry。

3.1 set()方法

  • 首先看一下set方法
    • 逻辑很简单,创建当前的线程根据当前的线程获取 ThreadLocalMap 实例对象,这个 ThreadLocalMap 是个什么了看这个跟 HashMap很相似啊,可别只看表面,这个可是没有一点亲戚关系,这点可别误解了,接着看得到当前线程的 map 第一次获取肯定是空。
    public void set(T value) { 
        Thread t = Thread.currentThread(); 
        ThreadLocalMap map = getMap(t); 
        if (map != null) 
            map.set(this, value); 
        else 
            createMap(t, value); 
    }
  • 然后看下getMap做了什么
    ThreadLocal.ThreadLocalMap threadLocals = null; 
    
    ThreadLocalMap getMap(Thread t) { 
        return t.threadLocals; 
    }
  • 找到这个变量第一次肯定是空, createMap(t, value);接着看:
    void createMap(Thread t, T firstValue) { 
        t.threadLocals = new ThreadLocalMap(this, firstValue); 
    }
  • 变量赋值操作,看下 ThreadLocalMap 的构造:
    • 很简单的吧,首先构造一个数组也就是我上边提到的 Entry 数组初始容量16,然后通过按位运算得到一个变量i值,构造一个 Entry 对象将 ThreadLocal 和 Value 放进去,将设置进去的值包装成一个对象存进数组里边,获取的时候在通过数组的 index 取出当前的对象
    private static final int INITIAL_CAPACITY = 16; 
    table = new Entry[INITIAL_CAPACITY]; 
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 
    table[i] = new Entry(firstKey, firstValue); 
    size = 1; 
    setThreshold(INITIAL_CAPACITY);
  • 接着看构造;
    static class Entry extends WeakReference<ThreadLocal> { 
        /** The value associated with this ThreadLocal. */ 
        Object value; 
    
        Entry(ThreadLocal k, Object v) { 
            super(k); 
            value = v; 
        } 
    }
  • 这个实体继承 WeakReference (就是我们常说的弱引用) v 就是我们设置进去的 value,现在整体的流程是不是很清楚了,然后我们在脑补一下,值是这样设置就去了,那么我获取的时候是不是只要得到 map 也就是 ThreadLocal.ThreadLocalMap threadLocals 这个变量,再根据这个变量得到当前线程的的数组,在通过数组的 index 是不是就可以得到当前的对象,再根据的对象取出 value。

3.2 get()方法

  • 看一下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(); 
    }
  • 然后看一下下面案例
    private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>(){ 
        @Override 
        protected Boolean initialValue() { 
            return true; 
        } 
    };
    button.setOnClickListener(new View.OnClickListener() { 
        @Override     public void onClick(View v) { 
            Log.i(TAG, "onClick: booleanThreadLocal------->"+booleanThreadLocal.get()); 
        } 
    });
  • 打印结果如下
    I/MainActivity: onClick: booleanThreadLocal------->true
    • 重写 initialValue 值返回true,这次我没有设置值,确返回true,是不是和get方法有关,当我们我们设置值的时候当前的线程的 map 为 null,这时 return setInitialValue();
  • 看下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; 
    }
  • 是不是一样的简单,逻辑很清楚,我们直接重写了 initialValue(),所以返回的 value 值就是我们重写方法的返回值;到此源码也就分析完毕了。

04.Handler为何用ThreadLocal

  • 再回过头来看下 Handler 消息机制,Handler 通过 Looper.loop() 方法轮询消息,我们一般写 Handler 的时候都是在主线程创建的,在程序启动的时候也就是加载ActivityThread类的时候系统已经帮我们自动的创建了主线程的Looper。
  • 可是如果我想在子线程创建Handler了,子线程是不是也需要自己的 Looper;是不是自己也需要启动 Looper.loop 来循环消息,这时候 ThreadLocal 这个类就来了,ThreadLocal 为当前的每一个线程存储一个 Looper,每一个 Looper 也有唯一的一个消息队列 MessageQueue,所以在子线程 new Handler() 的时候需要我们手动的去获取当前线程的 Looper,主动的调用Looper.prepare();
  • 看一下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)); 
    } 
    
    private Looper(boolean quitAllowed) { 
        mQueue = new MessageQueue(quitAllowed); 
        mThread = Thread.currentThread(); 
    }
  • 获取当前线程的 Looper 和 MessageQueue
    new Thread(new Runnable() { 
        private  Handler handler; 
        @Override     
        public void run() { 
            Looper.prepare(); 
            handler = new Handler(){ 
                @Override             
                public void handleMessage(Message msg) { 
                    Log.i(TAG, "handleMessage: "+Thread.currentThread().getName()+"workThread线程收到消息了-->"); 
                } 
            }; 
            handler.sendEmptyMessage(0x10000); 
            Looper.loop(); 
        } 
    }).start();
  • 或者来个暴力点的既然主线程已经有了Looper了就用他已经创建的好的
    new Thread(new Runnable() { 
        private  Handler handler; 
        @Override     
        public void run() { 
            handler = new Handler(Looper.getMainLooper()){ 
                @Override             
                public void handleMessage(Message msg) { 
                    Log.i(TAG, "handleMessage: "+Thread.currentThread().getName()+"main线程收到消息了-->"); 
                }
            }; 
            handler.sendEmptyMessage(0x10000); 
        } 
    }).start();
  • 上边两个方法都可以使 Handler在子线程去处理,但是接受消息的结果当然也是不一样的,上边的采用的子线程Looper,下边是 main 线程 Looper。
  • 其实在开发中用的ThreadLocal的地方极少,但是ThreadLocal也是不可忽视的一个重要点,hreadLocal在某些特殊的场景的,通过它可以实现一个看起来比较复杂的功能,当某些数据以线程为作用域并且不同线程要获取不同数据的时候,就可以用到 ThreadLocal;
贡献者: yangchong211
上一篇
4.7理解Handler同步屏障
下一篇
4.9ThreadLocal场景