Android源码解析Handler系列第(二)篇--- ThreadLocal详解

2024-09-02 15:08

本文主要是介绍Android源码解析Handler系列第(二)篇--- ThreadLocal详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上篇文章Android源码解析Handler系列第(一)篇说了Message的内部维持的全局池机制。这一篇仍然是准备知识,因为在Handler中有ThreadLocal的身影,大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper,所以,ThreadLocal是理解Looper的关键之一。

先看一下官方对这个类的解释

/*** 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>(){@Overrideprotected Boolean initialValue() {//初始值是falsereturn false;}};mBooleanThreadLocal.set(true);System.out.println("[Thread#main]mBooleanThreadLocal=" +mBooleanThreadLocal.hashCode()+"  "+ mBooleanThreadLocal.get());new Thread("Thread#1") {@Overridepublic void run() {mBooleanThreadLocal.set(false);System.out.println( "[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() +"  "+ mBooleanThreadLocal.get());};}.start();new Thread("Thread#2") {@Overridepublic 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>() {@Overrideprotected 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() {@Overridepublic 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类型的对象的弱引用,而Value则是线程需要保存的线程本地变量T。

 /** * 存放数据的数组。他存放数据的形式有点像map,是ThreadLocal与value相对应, 长度总是2的N次方*/private Object[] table;

table表结构

Value的主要API有下面几个
- void put(ThreadLocal

 /*** 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

这篇关于Android源码解析Handler系列第(二)篇--- ThreadLocal详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130304

相关文章

springboot security快速使用示例详解

《springbootsecurity快速使用示例详解》:本文主要介绍springbootsecurity快速使用示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录创www.chinasem.cn建spring boot项目生成脚手架配置依赖接口示例代码项目结构启用s

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

java中反射(Reflection)机制举例详解

《java中反射(Reflection)机制举例详解》Java中的反射机制是指Java程序在运行期间可以获取到一个对象的全部信息,:本文主要介绍java中反射(Reflection)机制的相关资料... 目录一、什么是反射?二、反射的用途三、获取Class对象四、Class类型的对象使用场景1五、Class

golang 日志log与logrus示例详解

《golang日志log与logrus示例详解》log是Go语言标准库中一个简单的日志库,本文给大家介绍golang日志log与logrus示例详解,感兴趣的朋友一起看看吧... 目录一、Go 标准库 log 详解1. 功能特点2. 常用函数3. 示例代码4. 优势和局限二、第三方库 logrus 详解1.

一文详解如何从零构建Spring Boot Starter并实现整合

《一文详解如何从零构建SpringBootStarter并实现整合》SpringBoot是一个开源的Java基础框架,用于创建独立、生产级的基于Spring框架的应用程序,:本文主要介绍如何从... 目录一、Spring Boot Starter的核心价值二、Starter项目创建全流程2.1 项目初始化(

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步