Android源码解析Handler系列第(二)篇--- ThreadLocal详解
2016-12-29 14:18
716 查看
在上篇文章Android源码解析Handler系列第(一)篇说了Message的内部维持的全局池机制。这一篇仍然是准备知识,因为在Handler中有ThreadLocal的身影,大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper,所以,ThreadLocal是理解Looper的关键之一。
先看一下官方对这个类的解释
大概意思就是说:实现了一个线程本地存储,也就是说,每个线程的一个变量,有自己的值。所有线程共享同一个 ThreadLocal对象,但每个线程访问它时,看到一个不同的值,如果一个线程改变了这个值,不影响其他线程,ThreadLocal支持NULL值。理解好费劲,只能说我翻译的太差!
重新解释一下:当工作于多线程中的对象使用ThreadLocal 维护变量时,ThreadLocal 为 每个使用该变量的线程分配一个独立的变量副本。每个线程独立改变自己的副本,而不影响其他线程所对应的变量副本。这就是它的基本原理,ThreadLocal主要的 API很简单。
public void set(T value):将值放入线程局部变量中
public T get():从线程局部变量中获取值
public void remove():从线程局部变量中移除值(有助于 JVM 垃圾回收)
protected T initialValue():返回线程局部变量中的初始值(默认为 null)
ThreadLocal为各个线程保存一个变量副本,这句话是关键,怎么理解?先来一个简单的DEMO
在上面的代码中,在主线程中设置mBooleanThreadLocal的值为true,在子线程1中设置mBooleanThreadLocal的值为false,在子线程2中不设置mBooleanThreadLocal的值,然后分别在3个线程中通过get方法去mBooleanThreadLocal的值,所以,主线程中应该是true,子线程1中应该是false,而子线程2中由于没有设置值,所以应该是初始值false。
再看一个DEMO
开了5个线程,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响。
读到这里,相信你对ThreadLocal的作用已经了解了,ThreadLocal和synchronized是有区别的,概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响,所以ThreadLocal和synchronized都能保证线程安全,但是应用场景却大不一样。
现在从源码中去找找答案,为什么各个线程都能保留一份副本,做到多并发的时候,线程互不影响呢?
ThreadLocal的构造函数是空的,啥也没有。
看一下里面的set方法
首先获得了当前线程实例,将实例传入values方法中,获得一个Values对象,第一次获得的Values对象是空的,就调用initializeValues初始化一个values对象,最后把当前对象作为key,value作为值,放进values中。现在的重点就是搞懂Values是什么?Values原来是ThreadLocal的静态内部类。在ThreadLocal.Values中有一个table成员,而这个table就以key,value的形式存储了线程的本地变量。key是ThreadLocal<T>类型的对象的弱引用,而Value则是线程需要保存的线程本地变量T。
table表结构
Value的主要API有下面几个
void put(ThreadLocal<?> key, Object value):往table里添加一个键值对
void add(ThreadLocal<?> key, Object value):也是往table里面添加键值对
Object getAfterMiss(ThreadLocal<?> key):在首位置没找到值的时候通过这个方法来找到给定key的值
void remove(ThreadLocal<?> key):删掉给定key对应的值
ThreadLocal.Values的其他成员
Values是调用put方法把传进来的键值对给存到table表里面去,这里不去分析它是怎么实现的了(可以移步http://blog.csdn.net/luoyanglizi/article/details/51510233)
上面是ThreadLocal存的过程,现在看取的过程。
首先取出当前线程的Values对象,跟set方法一样,如果这个对象为null那么就返回初始值, 如果Values对象不为null,那就取出它的table数组并找出ThreadLocal的reference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值。从可以看出,ThreadLocal的set和get方法所操作的对象都是当前线程的Values对象的table数组,如果一个变量使用了ThreadLocal,通过ThreadLocal的set和get方法,每一个线程的都会有一份这个变量的副本(键值对的形式进行存取),这就是为什么多线程并发的时候,线程互不影响的操作一个变量。
最后在举一个ThreadLocal的应用的例子:JDBC连接mysql数据库,把 Connection 放到了 ThreadLocal 中,这样每个线程之间就隔离了,不会相互干扰。
Please accept mybest wishes for your happiness and success
参考链接:
https://my.oschina.net/clopopo/blog/149368
http://blog.csdn.net/singwhatiwanna/article/details/48350919
https://my.oschina.net/huangyong/blog/159725
文/Looper景(简书作者)
原文链接:http://www.jianshu.com/p/411c40b09a81
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
先看一下官方对这个类的解释
/** * Implements a thread-local storage, that is, a variable for which each thread * has its own value. All threads share the same {@code ThreadLocal} object, * but each sees a different value when accessing it, and changes made by one * thread do not affect the other threads. The implementation supports * {@code null} values. * * @see java.lang.Thread * @author Bob Lee */
大概意思就是说:实现了一个线程本地存储,也就是说,每个线程的一个变量,有自己的值。所有线程共享同一个 ThreadLocal对象,但每个线程访问它时,看到一个不同的值,如果一个线程改变了这个值,不影响其他线程,ThreadLocal支持NULL值。理解好费劲,只能说我翻译的太差!
重新解释一下:当工作于多线程中的对象使用ThreadLocal 维护变量时,ThreadLocal 为 每个使用该变量的线程分配一个独立的变量副本。每个线程独立改变自己的副本,而不影响其他线程所对应的变量副本。这就是它的基本原理,ThreadLocal主要的 API很简单。
public void set(T value):将值放入线程局部变量中
public T get():从线程局部变量中获取值
public void remove():从线程局部变量中移除值(有助于 JVM 垃圾回收)
protected T initialValue():返回线程局部变量中的初始值(默认为 null)
ThreadLocal为各个线程保存一个变量副本,这句话是关键,怎么理解?先来一个简单的DEMO
public class ThreadLocalTest { public static void main(String[] args) throws InterruptedException { ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<Boolean>(){ @Override protected Boolean initialValue() { //初始值是false return false; } }; mBooleanThreadLocal.set(true); System.out.println("[Thread#main]mBooleanThreadLocal=" +mBooleanThreadLocal.hashCode()+" "+ mBooleanThreadLocal.get()); new Thread("Thread#1") { @Override public void run() { mBooleanThreadLocal.set(false); System.out.println( "[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() +" "+ mBooleanThreadLocal.get()); }; }.start(); new Thread("Thread#2") { @Override public void run() { System.out.println( "[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() + " "+mBooleanThreadLocal.get()); }; }.start(); } }
输出结果: [Thread#main]mBooleanThreadLocal=366712642 true [Thread#1]mBooleanThreadLocal=366712642 false [Thread#2]mBooleanThreadLocal=366712642 false
在上面的代码中,在主线程中设置mBooleanThreadLocal的值为true,在子线程1中设置mBooleanThreadLocal的值为false,在子线程2中不设置mBooleanThreadLocal的值,然后分别在3个线程中通过get方法去mBooleanThreadLocal的值,所以,主线程中应该是true,子线程1中应该是false,而子线程2中由于没有设置值,所以应该是初始值false。
再看一个DEMO
public class ThreadLocalTest { //创建一个Integer型的线程本地变量 public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[5]; for (int j = 0; j < 5; j++) { threads[j] = new Thread(new Runnable() { @Override public void run() { //获取当前线程的本地变量,然后累加5次 int num = local.get(); for (int i = 0; i < 5; i++) { num++; } //重新设置累加后的本地变量 local.set(num); System.out.println(Thread.currentThread().getName() + " : "+ local.get()); } }, "Thread-" + j); } for (Thread thread : threads) { thread.start(); } } }
输出结果: Thread-1 : 5 Thread-3 : 5 Thread-4 : 5 Thread-0 : 5 Thread-2 : 5
开了5个线程,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响。
读到这里,相信你对ThreadLocal的作用已经了解了,ThreadLocal和synchronized是有区别的,概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响,所以ThreadLocal和synchronized都能保证线程安全,但是应用场景却大不一样。
现在从源码中去找找答案,为什么各个线程都能保留一份副本,做到多并发的时候,线程互不影响呢?
ThreadLocal的构造函数是空的,啥也没有。
public ThreadLocal() { }
看一下里面的set方法
/** * Sets the value of this variable for the current thread. If set to * {@code null}, the value will be set to null and the underlying entry will * still be present. * * @param value the new value of the variable for the caller thread. */ public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
首先获得了当前线程实例,将实例传入values方法中,获得一个Values对象,第一次获得的Values对象是空的,就调用initializeValues初始化一个values对象,最后把当前对象作为key,value作为值,放进values中。现在的重点就是搞懂Values是什么?Values原来是ThreadLocal的静态内部类。在ThreadLocal.Values中有一个table成员,而这个table就以key,value的形式存储了线程的本地变量。key是ThreadLocal<T>类型的对象的弱引用,而Value则是线程需要保存的线程本地变量T。
/** * 存放数据的数组。他存放数据的形式有点像map,是ThreadLocal与value相对应, 长度总是2的N次方 */ private Object[] table;
table表结构
Value的主要API有下面几个
void put(ThreadLocal<?> key, Object value):往table里添加一个键值对
void add(ThreadLocal<?> key, Object value):也是往table里面添加键值对
Object getAfterMiss(ThreadLocal<?> key):在首位置没找到值的时候通过这个方法来找到给定key的值
void remove(ThreadLocal<?> key):删掉给定key对应的值
ThreadLocal.Values的其他成员
Values是调用put方法把传进来的键值对给存到table表里面去,这里不去分析它是怎么实现的了(可以移步http://blog.csdn.net/luoyanglizi/article/details/51510233)
/** * Sets entry for given ThreadLocal to given value, creating an * entry if necessary. */ void put(ThreadLocal<?> key, Object value) { cleanUp(); // Keep track of first tombstone. That's where we want to go back // and add an entry if necessary. int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }
上面是ThreadLocal存的过程,现在看取的过程。
/** * Returns the value of this variable for the current thread. If an entry * doesn't yet exist for this variable on this thread, this method will * create an entry, populating the value with the result of * {@link #initialValue()}. * * @return the current value of the variable for the calling thread. */ @SuppressWarnings("unchecked") public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); }
首先取出当前线程的Values对象,跟set方法一样,如果这个对象为null那么就返回初始值, 如果Values对象不为null,那就取出它的table数组并找出ThreadLocal的reference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值。从可以看出,ThreadLocal的set和get方法所操作的对象都是当前线程的Values对象的table数组,如果一个变量使用了ThreadLocal,通过ThreadLocal的set和get方法,每一个线程的都会有一份这个变量的副本(键值对的形式进行存取),这就是为什么多线程并发的时候,线程互不影响的操作一个变量。
最后在举一个ThreadLocal的应用的例子:JDBC连接mysql数据库,把 Connection 放到了 ThreadLocal 中,这样每个线程之间就隔离了,不会相互干扰。
public class DBUtil { // 数据库配置 private static final String driver = "com.mysql.jdbc.Driver"; private static final String url = "jdbc:mysql://localhost:3306/demo"; private static final String username = "xxx"; private static final String password = "xxx"; // 定义一个用于放置数据库连接的局部线程变量(使每个线程都拥有自己的连接) private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>(); // 获取连接 public static Connection getConnection() { Connection conn = connContainer.get(); try { if (conn == null) { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } } catch (Exception e) { e.printStackTrace(); } finally { connContainer.set(conn); } return conn; } // 关闭连接 public static void closeConnection() { Connection conn = connContainer.get(); try { if (conn != null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } finally { connContainer.remove(); } } }
Please accept mybest wishes for your happiness and success
参考链接:
https://my.oschina.net/clopopo/blog/149368
http://blog.csdn.net/singwhatiwanna/article/details/48350919
https://my.oschina.net/huangyong/blog/159725
文/Looper景(简书作者)
原文链接:http://www.jianshu.com/p/411c40b09a81
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
相关文章推荐
- Android源码解析Handler系列第(二)篇--- ThreadLocal详解
- Android源码解析Handler系列第(三)篇---深入了解Android的消息机制
- Android源码解析Handler系列第(一)篇 --- Message全局池
- Android源码解析Handler系列第(五)篇 ---HandlerThread你用过吗?
- Android源码解析Handler系列第(五)篇 ---HandlerThread你用过吗?
- Android源码解析Handler系列第(四)篇 --- 打破Handler那些困惑事儿
- 【Android源码系列】消息机制:Handler源码解析
- Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
- Android源码解析Handler系列第(四)篇 --- 打破Handler那些困惑事儿
- Android源码解析Handler系列第(一)篇 --- Message全局池
- Android Handler、Message完全解析,带你从源码的角度彻底理解
- Android的全局键(home键/长按耳机键)详解【android源码解析八】
- Android Handler、Message完全解析,带你从源码的角度彻底理解
- Android Handler、Message完全解析,带你从源码的角度彻底理解
- android源码解析--Handler
- Android的全局键(home键/长按耳机键)详解【android源码解析八】
- UiAutomator系列——Appium Android Bootstrap源码分析之命令解析执行(008)
- Android Handler、Message完全解析,带你从源码的角度彻底理解
- Android中音乐文件的信息详解【安卓源码解析二】
- Android的全局键(home键/长按耳机键)详解【android源码解析八】