线程之ThreadLocal使用

 

1.简介-2.使用流程- 3.额外补充:ThreadLocal与同步机制区别;4.实现原理ThreadLocal如何做到线程安全 5.具体使用
1. 简介
定义:线程的局部变量
作用:为每个线程提供一个特定空间(即该变量),以保存该线程所独享的资源
应用场景:隔离线程& 防止 线程间的数据资源共享
特别注意:
a.使每个线程都可独立地改变自己空间内的资源(即设置、存储的值),而不会和其他线程的资源冲突。
b. 1个变量只能被同一个线程读写。若两个线程同时执行一段含有1个ThreadLocal变量引用的代码,它们也无法访问到对方的ThreadLocal变量。

2. 使用流程
主要是创建ThreadLocal变量 & 访问ThreadLocal变量
2.1 创建ThreadLocal变量3种方式
// 1. 直接创建对象
private ThreadLocal myThreadLocal = new ThreadLocal()
// 2. 创建泛型对象
private ThreadLocal myThreadLocal = new ThreadLocal<String>();
// 3. 创建泛型对象 & 初始化值
// 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "This is the initial value";
}
};
// 特别注意:
// 1. ThreadLocal实例 = 类中的private、static字段
// 2. 只需实例化对象一次 & 不需知道它是被哪个线程实例化
// 3. 每个线程都保持 对其线程局部变量副本的隐式引用
// 4. 线程消失后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
// 5. 虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程只能访问到自己通过调用ThreadLocal的set()设置的值
// 即 哪怕2个不同的线程在同一个`ThreadLocal`对象上设置了不同的值,他们仍然无法访问到对方的值

2.2 访问ThreadLocal变量
//1. set(),需要传入一个Object类型的参数
myThreadLocal.set("初始值”);
//2. 读取ThreadLocal变量中的值:get() 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();

3. 额外补充
3.1 ThreadLocal如何做到线程安全
a.每个线程拥有自己独立的ThreadLocals变量(指向ThreadLocalMap对象 )
b.每当线程 访问 ThreadLocals变量时,访问的都是各自线程自己的ThreadLocalMap变量(键 - 值)
c.ThreadLocalMap变量的键 key = 唯一 = 当前ThreadLocal实例
上述3点 保证了线程间的数据访问隔离,即线程安全。
public class ThreadLocalTest {
public static void main(String[] args){
// 新开2个线程用于设置 & 获取 ThreadLoacl的值
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "线程1").start();
new Thread(runnable, "线程2").start();
}
public static class MyRunnable implements Runnable {
private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 运行线程时,分别设置 & 获取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 设置值 = 线程名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
测试结果
线程1:线程1的threadLocal
线程2:线程2的threadLocal
// 从上述结果看出,在2个线程分别设置ThreadLocal值 & 分别获取,结果并未互相干扰

3.2 与同步机制的区别?
ThreadLocal模式:
实现机制:为每个线程都提供一个变量的副本,从而实现同时访问而互不影响(即以空间换时间);
同步共享:无必要对该变量进行同步(每个线程都拥有自己的变量副本)
应用场景:隔离多个线程的数据资源共享

同步机制:
实现机制:提供一个变量,让不同的线程排队访问(即以时间换空间)
同步共享:被用作"锁机制"的变量是多个线程共享的
a.通过对象的锁机制(synchronized)保证同一时间只有一个线程访问变量
b.synchronized = 1个保留字:依靠JVM的锁机制来实现临界区的函数 or 变量的访问中的原子性
应用场景:同步多个线程 对相同资源的并发访问,防止并发冲突(可理解为一种多线程的通信方式)。

4. 实现原理-核心原理:
ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值
1.ThreadLocalMap的键Key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值
2.该key是 ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该 key的引用而清理掉ThreadLocal对象
关于如何设置 & 获取 ThreadLocal变量里的值,具体请看下面的源码分析。
请直接看代码注释
// ThreadLocal的源码
public class ThreadLocal<T> {
...
/**
* 设置ThreadLocal变量引用的值
* ThreadLocal变量引用 指向 ThreadLocalMap对象,即设置ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
* ThreadLocalMap的键Key = 当前ThreadLocal实例
* ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
**/
public void set(T value) {
// 1. 获得当前线程
Thread t = Thread.currentThread();
// 2. 获取该线程的ThreadLocalMap对象 ->>分析1
ThreadLocalMap map = getMap(t);
// 3. 若该线程的ThreadLocalMap对象已存在,则替换该Map里的值;否则创建1个ThreadLocalMap对象
if (map != null)
map.set(this, value);// 替换
else
createMap(t, value);// 创建->>分析2
}
/**
* 获取ThreadLocal变量里的值
* 由于ThreadLocal变量引用 指向 ThreadLocalMap对象,即获取ThreadLocalMap对象的值 = 该线程设置的存储在ThreadLocal变量的值
**/
public T get() {
// 1. 获得当前线程
Thread t = Thread.currentThread();
// 2. 获取该线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 3. 若该线程的ThreadLocalMap对象已存在,则直接获取该Map里的值;否则则通过初始化函数创建1个ThreadLocalMap对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value; // 直接获取值
}
return setInitialValue(); // 初始化
}
// 初始化ThreadLocal的值
private T setInitialValue() {
T value = initialValue();
// 1. 获得当前线程
Thread t = Thread.currentThread();
// 2. 获取该线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 3. 若该线程的ThreadLocalMap对象已存在,则直接替换该值;否则则创建
if (map != null)
map.set(this, value); // 替换
else
createMap(t, value); // 创建->>分析2
return value;
}
// 分析1:获取当前线程的threadLocals变量引用
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//分析2:创建当前线程的ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
// 新创建1个ThreadLocalMap对象 放入到 Thread类的threadLocals变量引用中:
// a. ThreadLocalMap的键Key = 当前ThreadLocal实例
// b. ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
// 即 threadLocals变量 属于 Thread类中 ->> 分析3
}
...
}
// 分析3:Thread类 源码分析
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
// 即 Thread类持有threadLocals变量
// 线程类实例化后,每个线程对象拥有独立的threadLocals变量变量
// threadLocals变量在 ThreadLocal对象中 通过set() 或 get()进行操作
...
}

5. 具体使用
public class ThreadLocalTest {
public static void main(String[] args){
// 新开2个线程用于设置 & 获取 ThreadLoacl的值
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "线程1").start();
new Thread(runnable, "线程2").start();
}
public static class MyRunnable implements Runnable {
private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 运行线程时,分别设置 & 获取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 设置值 = 线程名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
测试结果
线程1:线程1的threadLocal
线程2:线程2的threadLocal
// 从上述结果看出,在2个线程分别设置ThreadLocal值 & 分别获取,结果并未互相干扰。

一. 线程之ThreadLocal使用

Android线程管理之ThreadLocal理解及应用场景

前言:
那么为何要使用ThreadLocal呢? ThreadLocal是什么呢? 它能解决什么样的问题呢?

ThreadLocal介绍:
ThreadLocal它的真正意思是线程本地变量。
ThreadLocal定义:
实现一个线程本地的存储,也就是说,每个线程都有自己的局部变量。所有线程都共享一个ThreadLocal对象,
但是每个线程在访问这些变量的时候能得到不同的值,每个线程可以更改这些变量并且不会影响其他的线程,并且支持null值。     

1. ThreadLocal理解:

通过上面的理解总结几点:
1.每个线程读拥有自己的局部变量
        每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的
2.独立于变量的初始化副本,或者初始化一个属于自己的变量
ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝,同样也可以new的方式为线程创建一个变量
3.变量改变只与当前线程关联,线程之间互不干扰。
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就可以解决问题。

Android属性动画的AnimationHandler存取使用了ThreadLocal;
Handler消息机制的Looper存储也是采用ThreadLocal,EventBus存储当前线程下的发送事件队列状态也采用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() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。
各个线程中访问的是不同的对象,改变的也是自己独立的对象,本身就不属于同一个对象,没有共享的概念,更加不可能是解决共享对象多线程访问的。

ThreadLocal使用举例?
举例,让每个线程拥有自己唯一的一个任务队列,类似EventBus的实现。
总结:
个人建议看下EventBus中使用ThreadLocal范例,用的很巧妙又很容易让人理解,只是ThreadLocal本身我们在日常项目开发中使用的比较少,很难找到合适场景来搞懂它。

  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;
    @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>保存也是可以的,上面也说了相对比较麻烦。

  线程管理相关文章地址:

posted on 2018-06-25 17:34  左手指月  阅读(7027)  评论(1编辑  收藏  举报