Android消息机制之ThreadLocal原理解析

来源:互联网 发布:战地4网络对战 编辑:程序博客网 时间:2024/05/20 07:31



今日科技快讯


随着圣诞节来临,到处都是浓浓的节日氛围。近日,用户反映在大街上扫码摩拜单车,开锁时有可能听到“叮叮当、叮叮当、铃儿响叮当”的圣诞歌。摩拜方面表示,称这确实是官方准备的“小惊喜”。目前,摩拜单车音乐铃声已经陆续在全国各个城市上线,圣诞节期间只要扫码摩拜单车,都有机会听到音乐铃声,并且用户骑行次数越多,触发音乐铃声的机会越多。


作者简介


本篇来自 木瓜糖 的投稿,分享了Android消息机制 ThreadLocal 数据存储的原理解析,希望大家喜欢!

木瓜糖的博客地址:

https://www.jianshu.com/u/559c1e54ae74


前言


说起Android消息机制,老生常谈的问题,Handler ,Looper,MessageQueue,Message这几个都是离不开的话题,不同的类承载不同的功能,首先还是简单的总结下各自的功能:

  • Handler:发送和处理消息

  • MessageQueue:消息队列,采用单链表结构存储数据

  • Looper:调用loop()轮询消息

  • Message:需要发送的内容,消息

画一张流程图显示:

当然上边只是简单的分析了下 Handler 一个发送和处理的一个流程,只是让大家温故下,下面正式的开始介绍这个ThreadLocal 重量级角色。


ThreadLocal 介绍


定义:早在JDK 1.2的版本中就提供 java.lang.ThreadLocal,ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,百度百科一下就知道,当然这只是定义,先上一段代码瞧瞧:

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 方法,但是他们打印的值却是不一样,很奇妙吧,这也就是他的奇妙之处。

首先简答的介绍为什么会产生这种效果,方便后边更好的看代码,ThreadLocal 内部维护了一个针对每一个线程的数组 Entry[],它的初始容量是16,我们在设置 value 的时候将当前的 value 值封装 Entry 类里面,在然后再根据当前的 ThreadLocal 的索引去查中对应的 Entry 值,最终根据 Entry 对象取出 value 值,很明显每个线程的数组是不相同,所以就可以取出不同的 Entry,这么说肯定还是不是特别明白,没关系这个只是开胃菜,提前有个了解有印象就行,我们看下代码。


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

逻辑很简单,创建当前的线程根据当前的线程获取 ThreadLocalMap 实例对象,这个 ThreadLocalMap 是个什么了看这个跟 HashMap 很相似啊,可别只看表面,这个可是没有一点亲戚关系,这点可别误解了,接着看得到当前线程的 map 第一次获取肯定是空:
我们看下 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 的构造:

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

很简单的吧,首先构造一个数组也就是我上边提到的 Entry 数组初始容量16,然后通过按位运算得到一个变量i值,构造一个 Entry 对象将 ThreadLocal 和 Value 放进去,将设置进去的值包装成一个对象存进数组里边,获取的时候在通过数组的 index 取出当前的对象,这样一分析是不是有种明白的感觉别急,接着看构造;

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,思路是没有错,我们看下源码是不是这个样子了


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());     } });
11-24 08:02:02.332 26018-26018/com.example.administrator.dbhelp I/MainActivity: onClick: booleanThreadLocal------->true

重写 initialValue 值返回 true,这次我没有设置值,确返回 true,是不是和 get 方法有关,当我们我们设置值的时候当前的线程的 map 为 null,这时 return 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 值就是我们重写方法的返回值;到此源码也就分析完毕了,这时我们再回过头来看下 Handler 消息机制,Handler 通过 Looper.loop() 方法轮询消息,我们一般写 Handler 的时候都是在主线程创建的,在程序启动的时候也就是加载 ActivityThread 类的时候系统已经帮我们自动的创建了主线程的 Looper,通过上边的图也可知,可是如果我想在子线程创建 Handler了,子线程是不是也需要自己的 Looper;是不是自己也需要启动 Looper.loop 来循环消息,这时候 ThreadLocal 这个类就来了,ThreadLocal 为当前的每一个线程存储一个 Looper,每一个 Looper 也有唯一的一个消息队列 MessageQueue,所以在子线程 new Handler() 的时候需要我们手动的去获取当前线程的 Looper,主动的调用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)); } ------------------------------------------- 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 也是不可忽视的一个重要点,在面试的时候你能把 ThreadLocal 和 Looper 结合起来一起讲,也许就是加分项,再者 ThreadLocal 在某些特殊的场景的,通过它可以实现一个看起来比较复杂的功能,当某些数据以线程为作用域并且不同线程要获取不同数据的时候,就可以用到 ThreadLocal;


欢迎长按下图 -> 识别图中二维码

或者 扫一扫 关注我的公众号

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 丸美效果怎么样 marubi 瞳话眼霜怎么样 完美眼霜 芦荟胶多少钱 完美芦荟胶的功效与作用 芦荟膏 芦荟胶是什么 完美芦荟胶专卖店 完美芦荟胶 完美芦荟胶真假辨别 完美芦荟膏 卢会胶 完美芦荟胶怎么样 芦苇胶 完美芦荟胶作用 完美商城 完美专卖店 完美旗舰店 完美芦荟胶图片 完美芦荟胶怎么用 完美芦荟胶的用法 茹荟胶 卢荟胶 完美芦荟胶好用吗 完美芦荟胶多少钱 完美芦荟胶价格 完美芦荟胶的作用 完美茹荟胶 完美芦荟胶真假 卢荟胶的功效与作用 完美卢荟胶 完美牌芦荟胶 完美芦荟胶成分 大宝sod蜜 芦荟胶 完美芦荟胶可以吃吗 芦荟凝胶的功效与作用 田丸美久 丸荣2h2d倍力挺 丸荣2h2d效果怎么样