前言:

最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣!查阅了一下资料发现Android最重要的Handler消息机制里面的Looper存储也是采用ThreadLocal,开源框架EventBus存储当前线程下的发送事件队列状态也是采用ThreadLocal,那么为何要使用ThreadLocal呢?ThreadLocal是什么呢?它能解决什么样的问题呢?带着这么疑问来学习下ThreadLocal。

线程管理相关文章地址:

ThreadLocal介绍

ThreadLocal如果单纯从字面上理解的话好像是“本地线程”的意思,其实并不是这个意思,只是这个名字起的太容易让人误解了,它的真正的意思是线程本地变量。看看官方怎么说的。

/**
* 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对象,但是每个线程在访问这些变量的时候能得到不同的值,每个线程可以更改这些变量并且不会影响其他的线程,并且支持null值。

ThreadLocal理解

我们先看下属性动画为每个线程设置AnimationHeadler的

    private static AnimationHandler getOrCreateAnimationHandler() {
AnimationHandler handler = sAnimationHandler.get();
if (handler == null) {
handler = new AnimationHandler();
sAnimationHandler.set(handler);
}
return handler;
}

因为protected static ThreadLocal<AnimationHandler> sAnimationHandler =new ThreadLocal<AnimationHandler>();这里没有采用初始化值,这里不是通过一个变量的拷贝而是每个线程通过new创建一个对象出来然后保存。很多人认为ThreadLocal是为了解决共享对象的多线程访问问题的,这是错误的说法,因为无论是通过初始化变量的拷贝还是直接通过new创建自己局部变量,ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象,改变的也是自己独立的对象,本身就不属于同一个对象,没有共享的概念,更加不可能是解决共享对象的多线程访问的。

通过上面的理解总结以下几点

 1.每个线程读拥有自己的局部变量

每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的

 2.独立于变量的初始化副本,或者初始化一个属于自己的变量

ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝,同样也可以new的方式为线程创建一个变量

3.变量改变只与当前线程关联,线程之间互不干扰

ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制。

所以ThreadLocal既不是为了解决共享多线程的访问问题,更不是为了解决线程同步问题,ThreadLocal的设计初衷就是为了提供线程内部的局部变量,方便在本线程内随时随地的读取,并且与其他线程隔离。

ThreadLocal使用场景

说了那么多的概念,归根到底我们在什么时候才使用ThreadLocal呢?很多时候我们会创建一些静态域来保存全局对象,那么这个对象就可能被任意线程访问,如果能保证是线程安全的,那倒是没啥问题,但是有时候很难保证线程安全,这时候我们就需要为每个线程都创建一个对象的副本,我们也可以用ConcurrentMap<Thread, Object>来保存这些对象,这样会比较麻烦,比如当一个线程结束的时候我们如何删除这个线程的对象副本呢?如果使用ThreadLocal就不用有这个担心了,ThreadLocal保证每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。经查阅资料大致得到以下两种场景:

1.)当某些数据以线程为作用域,并且不同线程拥有不同数据副本的时候。

ThreadLocal使用场合主要解决多线程中数据因并发产生不一致的问题。ThreadLocal以空间换时间,为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来的线程消耗,也减少了线程并发控制的复杂度。

例如Android的Handler消息机制,对于Handler来说,它需要获取当前线程的looper很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。再例如开源框架EventBus,EventBus需要获取当前线程的PostingThreadState对象,不同的PostingThreadState同样作用于不同的线程,EventBus可以很轻松的获取当前线程下的PostingThreadState对象,然后进行相关操作。

2.)复杂逻辑下对象传递,比如监听器的传递

使用参数传递的话:当函数调用栈更深时,设计会很糟糕,为每一个线程定义一个静态变量监听器,如果是多线程的话,一个线程就需要定义一个静态变量,无法扩展,这时候使用ThreadLocal就可以解决问题。

ThreadLocal使用举例

举一个简单的例子,让每个线程拥有自己唯一的一个任务队列,类似EventBus的实现。

  private static final ThreadLocal<PriorityQueue<TaskItem>> queueThreadLocal = new ThreadLocal<PriorityQueue<TaskItem>>() {
@Override
protected PriorityQueue<TaskItem> initialValue() {
return new PriorityQueue<>(5);
}
}; public PriorityQueue<TaskItem> getTaskQueue() {
PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
return taskItems;
} public void addTask(TaskItem taskItem) {
PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
taskItems.add(taskItem);
} public void removeTask(TaskItem taskItem) {
PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
if (taskItems.contains(taskItem)) {
taskItems.remove(taskItem);
}
} private void exceTask() {
PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
if (!taskItems.isEmpty()) {
TaskItem taskItem = taskItems.poll();
taskItem.exceTask();
}
}

附上TaskItme代码:

public class TaskItem implements Comparable {
private long Id;
private String name;
private int priority; public long getId() {
return Id;
} public void setId(long id) {
Id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getPriority() {
return priority;
} public void setPriority(int priority) {
this.priority = priority;
} @Override
public int compareTo(Object arg0) {
if (TaskItem.class.isInstance(arg0)) {
TaskItem tm = (TaskItem) arg0;
if (tm.priority > priority) {
return -1;
} else if (tm.priority < priority) {
return 1;
}
}
return 0;
} public void exceTask() {
Log.e("exceTask", "exceTask---id:" + Id + " name:" + name);
} }

经过上面代码可以看到,你是在哪个线程提交的任务自然而然的就添加到线程所属的任务队列里面,这里其实通过ConcurrentMap<Thread, Object>保存也是可以的,上面也说了相对比较麻烦。

总结:

由于对ThreadLocal了解不是很深刻,仅仅理解那么一点点,也许有些观点不一定正确,希望看到的朋友批评指正谢谢。如果大家想通过一个例子来学习的话,个人建议看下EventBus中使用ThreadLocal范例,用的很巧妙又很容易让人理解,只是ThreadLocal本身我们在日常项目开发中使用的比较少,一会半会的很难找到合适的场景来搞懂它。

Android线程管理之ThreadLocal理解及应用场景的更多相关文章

  1. Android线程管理之Thread使用总结

    前言 最近在一直准备总结一下Android上的线程管理,今天先来总结一下Thread使用. 线程管理相关文章地址: Android线程管理之Thread使用总结 Android线程管理之Executo ...

  2. Android线程管理之ExecutorService线程池

    前言: 上篇学习了线程Thread的使用,今天来学习一下线程池ExecutorService. 线程管理相关文章地址: Android线程管理之Thread使用总结 Android线程管理之Execu ...

  3. Android线程管理之ThreadPoolExecutor自定义线程池

    前言: 上篇主要介绍了使用线程池的好处以及ExecutorService接口,然后学习了通过Executors工厂类生成满足不同需求的简单线程池,但是有时候我们需要相对复杂的线程池的时候就需要我们自己 ...

  4. Android线程管理之AsyncTask异步任务

    前言: 前面几篇文章主要学习了线程以及线程池的创建与使用,今天来学习一下AsyncTask异步任务,学习下AsyncTask到底解决了什么问题?然而它有什么弊端?正所谓知己知彼百战百胜嘛! 线程管理相 ...

  5. Android线程管理(二)——ActivityThread

    线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...

  6. Android线程管理(一)——线程通信

    线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...

  7. Android线程管理(三)——Thread类的内部原理、休眠及唤醒

    线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...

  8. android线程池ThreadPoolExecutor的理解

    android线程池ThreadPoolExecutor的理解 线程池 我自己理解看来.线程池顾名思义就是一个容器的意思,容纳的就是ThreadorRunable, 注意:每一个线程都是需要CPU分配 ...

  9. Android线程管理(三)——Thread类的内部原理、休眠及唤醒

    线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...

随机推荐

  1. 从0开始搭建SQL Server AlwaysOn 第四篇(配置异地机房节点)

    从0开始搭建SQL Server AlwaysOn 第四篇(配置异地机房节点) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://www ...

  2. Concepts:Request 和 Task

    当SQL Server Engine 接收到Session发出的Request时,SQL Server OS将Request和Task绑定,并为Task分配一个Workder.在TSQL Query执 ...

  3. 如何选择PHP框架?

    PHP是世界上最受欢迎的编程语言之—.最近发布的PHP7令这种服务器的编程语言比以前变得更好,更稳定了. PHP被广泛应用于重大的项目.例如Facebook就是使用PHP来维护和创建它们的内部系统的. ...

  4. 主成分分析(PCA)原理总结

    主成分分析(Principal components analysis,以下简称PCA)是最重要的降维方法之一.在数据压缩消除冗余和数据噪音消除等领域都有广泛的应用.一般我们提到降维最容易想到的算法就 ...

  5. 【原创分享·微信支付】 C# MVC 微信支付教程系列之公众号支付

    微信支付教程系列之公众号支付         今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付.现在,我们讲讲这个公众号支付.公众号支付的应用环境常见的用户通过公众号,然后再通 ...

  6. nodejs项目mysql使用sequelize支持存储emoji

    nodejs项目mysql使用sequelize支持存储emoji 本篇主要记录nodejs项目阿里云mysql如何支持存储emoji表情. 因由 最近项目遇到用户在文本输入emoji进行存储的时候导 ...

  7. MySQL 系列(三)你不知道的 视图、触发器、存储过程、函数、事务、索引、语句

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 第三篇:MySQL 系列(三)你不知道的 视图.触发器.存储过程.函数 ...

  8. angluarjs2项目生成内容合并到asp.net mvc4项目中一起发布

    应用场景 angular2(下文中标注位NG2)项目和.net mvc项目分别开发,前期采用跨域访问进行并行开发,后期只需要将NG2项目的生产版本合并到.net项目. NG2项目概述 ng2项目采用的 ...

  9. 免费道路 bzoj 3624

    免费道路(1s 128MB)roads [输入样例] 5 7 21 3 04 5 13 2 05 3 14 3 01 2 14 2 1 [输出样例] 3 2 04 3 05 3 11 2 1 题解: ...

  10. Lind.DDD.LindMQ的一些想法

    回到目录 很久就想写一套属于自己的消息队列组件,前段时候看了汤雪华同学的EQueue,感觉还是不错的,他也是看了rabbitMQ之后写的Equeue,在设计上与前者有类似的地方,而大叔这次准备写一个L ...