Java&Android

您所在的位置:网站首页 数据集实例化什么意思 Java&Android

Java&Android

2023-05-06 03:26| 来源: 网络整理| 查看: 265

ThreadLocal是什么

ThreadLocal是一个能创建线程局部变量的类。通过ThreadLocal提供的get和set方法,可以为每一个使用该变量的线程保存一份数据副本,且线程之间是不能相互访问的,从而达到变量在线程间隔离、封闭的效果。

使用例子 public static void main(String[] args) throws InterruptedException { final ThreadLocal threadLocal = new ThreadLocal(); threadLocal.set("AAA"); new Thread(new Runnable() { @Override public void run() { threadLocal.set("BBB"); System.out.println("get in " + Thread.currentThread().getName() + " " + threadLocal.get()); } }).start(); Thread.sleep(1000); System.out.println("get in main thread " + threadLocal.get()); } 复制代码

执行结果:

get in Thread-0 BBB get in main thread AAA 复制代码

首先,在主线程中初始化了ThreadLocal,并且操作的变量是String类型,在主线程中设置该变量为"AAA",主线程等待1秒钟,同时启动了一个子线程也调用ThreadLocal设置该变量为"BBB"并输出,1秒之后通过get输出主线程的结果,发现子线程设置的值并没有影响主线程中设置的值,即通过ThreadLocal修饰的变量可以实现在各个线程之间互不干扰,相互隔离的效果。

源码解析 初始化 //1 final ThreadLocal threadLocal = new ThreadLocal(); threadLocal.set("AAA"); 复制代码 //2 final ThreadLocal threadLocal = new ThreadLocal() { @Override protected String initialValue() { return "AAA"; } }; 复制代码

对应的源码:

protected T initialValue() { return null; } public ThreadLocal() { } 复制代码

ThreadLocal的初始化可以有上面1、2两种方式,一种是先初始化然后通过set设置值,一种直接重写initialValue并设置值。既然ThreadLocal可以做到变量的线程封闭,我们有理由猜想是不是ThreadLocal是通过Map来实现的呢?其中key是当前Thread,value是通过set或者initialValue设置的,看似是这样,但ThreadLocal内部并不是这么实现的,接着往下分析。

set值 public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的 ThreadLocalMap map = getMap(t); if (map != null) //不为空,内部直接通过数组设置Entry元素(Entry中包装了ThreadLocal及value,其中key=ThreadLocal,value=传入值value) map.set(this, value); else //为空,则初始化一个ThreadLocalMap,并将ThreadLocal及value包装成Entry放入数组中。 createMap(t, value); } ThreadLocalMap getMap(Thread t) { //threadLocals是Thread类中的成员变量 return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 复制代码

Thread类:

public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; } 复制代码

所以set方法首先根据当前线程获取线程中的threadLocals变量(ThreadLocalMap类型),并将ThreadLocal及value包装成Entry放入数组中,因为threadLocals是Thread中的局部变量(存放在栈空间中),所以只有当前线程能访问,其他线程无法访问。这里有个问题:为什么还需要将ThreadLocal作为key传入到ThreadLocalMap呢?因为一个线程中可以初始化多个ThreadLocal,是一对多的关系,所以需要传入ThreadLocal,如果初始化了多个ThreadLocal,根据不同的ThreadLocal可以获得对应的value。那么ThreadLocalMap内部到底是怎么存储的呢?

ThreadLocal静态内部类ThreadLocalMap:

static class ThreadLocalMap { //内部类Entry,继承了弱引用WeakReference,使用ThreadLocal作为键值 static class Entry extends WeakReference k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } //根据key(ThreadLocal类型)设置value值 private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; //获取数组中存取位置 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); //如果key值在Entry中存在,那么直接覆盖之前的值 if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } //移除key对应的value private void remove(ThreadLocal key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } } 复制代码 get值 public T get() { //获取当前thread Thread t = Thread.currentThread(); //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的 ThreadLocalMap map = getMap(t); if (map != null) { //根据this(ThreeadLocal)获取数组中对应的Entry,不为空直接取出value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } //如果线程中的ThreadLocalMap为空,则进行初始化 return setInitialValue(); } private T setInitialValue() { //初始化值 默认是null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } 复制代码 ThreadLocal在Handler中的使用

Handler机制:

https://upload-images.jianshu.io/upload_images/587163-894d1f0e7c5d4920.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200

Handler构造函数:

public Handler(Callback callback, boolean async) { ......其他代码...... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 复制代码

Looper.prepare初始化:

static final ThreadLocal sThreadLocal = new ThreadLocal(); 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)); } 复制代码

Android中Handler机制在项目中使用的很频繁,Handler底层通过MessageQueue和Looper来实现消息的线程间通信。其中Handler来发送及接收并处理消息,MessageQueue接收Handler发来的消息,并在Looper循环中根据msg.target(handler)来分发消息。一个线程只对应一个Lopper,一个Looper对应一个MessageQueue,但是一个线程中可以有多个Handler。因为一个线程只能对应一个Looper,且Looper跟线程是一一绑定关系,此时用ThreadLocal再合适不过。

Looper中使用ThreadLocal关联Looper,使得Looper只能在各自线程使用,并且不管handler从哪个线程传来消息,ThreadLocal保证了最终消息在Looper初始化时所在的线程处理。

总结 ThreadLocal存储变量副本实际是保存在每个线程的threadLocals(ThreadLocal.ThreadLocalMap类型)变量中。 ThreadLocal包含的对象(指的是ThreadLocal中的T对象)在不同的线程中有不同的副本(实际上也是不同的实例) ThreadLocalMap中的Entry弱引用于ThreadLocal,同时也会回收key为null的Entry,从而避免了Entry无法释放导致内存泄漏

画一个简易图:

threadLocal

参考

【1】droidyue.com/blog/2016/0… 【2】www.cnblogs.com/dolphin0520… 【3】www.jasongj.com/java/thread… 【4】juejin.im/post/5ba64d…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3