ThreadLocal相关整理
JDK1.2提供
- 根据JDK文档中的解释:
ThreadLocal的作用是提供线程内的局部变量,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立改变该变量的副本,而不会影响其他线程所对应的副本。
ThreadLocal的使用:
可以看到我们用ThreadLocal存放了一个String字符串,在不同的线程set数值后,只在当前线程管用,所以说,如同上述所说的
- 一个ThreadLocal可以被多个线程共享
- 每个线程对同一个ThreadLocal的set get操作只针对当前线程管用
ThreadLocal的原理以及源码介绍
大概了解了ThreadLocal如何使用,那么请问,ThreadLocal如何保证不同线程的独立性的呢?
ThreadLocal几个内部方法
protected T initialValue()(如果不想初始值返回null,需要重写initialValue方法)
1 | protected T initialValue() { |
public T get()(该方法返回当前线程变量副本。如果这是线程第一次调用该方法,则创建并初始化此副本。)
/**
1 | * Returns the value in the current thread's copy of this |
public void set(T value)
/**
1 | * Sets the current thread's copy of this thread-local variable |
- 由get和set源码可以看出,数据的存取都是先获取ThreadLocalMap对象,从ThreadLocalMap存取
- ThreadLocalMap是一个map,它的key,就是threadLocal本身,值就是存放的变量副本
- 每个线程对应一个本地变量的map,每个可以存放多个线程本地变量(即不同的ThreadLocal)
public void remove()(jdk1.5后出现)
/**
1 | * Removes the current thread's value for this thread-local |
通过remove源码可以看到,
- 先通过ThreadLocal的getMap(Thread.currentThread())方法拿到当前线程的ThreadLocalMap
- 然后再在当前线程的ThreadLocalMap中get,set,remove
关于remove需要知道的几点:
- 为什么移除某个ThreadLocal的值:
目的是减少内存缓存,remove之后如果再次访问此线程局部变量的值,将返回initiValue初始值
线程结束后,该线程对应的所有局部变量将自动被垃圾回收,但是显示调用remove清楚线程局部变量不是必须操作,但是可以加快内存回收的速度
ThreadLocal和同步机制synchonzied的区别
- ThreadLocal:以空间换时间
- synchonzied:以时间换空间
synchonzied同步机制:
为多线程对相同资源的并发访问控制,保证了多线程之间的数据共享,同步会带来巨大的性能开销,所以同步操作应该是细粒度的(对象中的不同元素使用不同的锁,而不是整个对象一个锁),以时间换空间的意思是:使用同步真正的风险是复杂性和可能破坏资源安全,而不是性能。
ThreadLocal线程局部变量机制:
空间换取时间,不同线程访问同一ThreadLocal,数据的存取是当前线程的数据副本,也就是说不同线程在某一时间访问到的并不是同一对象,所以效率比较高,但是占用内存比较大,当线程结束之后,remove会加快内存的回收速度。
Synchronized着重于线程间的数据共享,而ThreadLocal则着重于线程间的数据隔离。
ThreadLocal的弊端(内存泄露)
内存泄露原因
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部的强引用,那么在系统GC的时候,这个ThreadLocal就会被回收掉
ThreadLocal被回收掉之后,那么当前Thread的ThreadLocalMap中间就会出现key为null的Entry
key为null的话就意味着,没有办法访问这些key对应的值,就会存在以下的这样一个强引用链
value —Entry—TreadLocalMap–Thread
内存泄露解决
ThreadLocalMap
的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal
的get()
,set()
,remove()
的时候都会清除线程ThreadLocalMap
里所有key
为null
的value
。
以下操作会导致内存泄露
使用static的ThreadLocal,延长了
ThreadLocal
的生命周期,导致某个线程Thread结束后,但是Thread内部的ThreadLocalMap中存在这个静态的ThreadLocal,导致ThreadLocalMap没法被回收,导致该Thread没法被回收分配使用了
ThreadLocal
又不再调用get()
,set()
,remove()
方法,那么就会导致内存泄漏。因为如上所说get()
,set()
,remove()
会清理线程ThreadLocalMap里所有key为null的value
Android中ThreadLocal的体现
Handler消息机制
熟悉Handler机制的都知道
在ActivityThread的main方法中Looper.prepareMainLooper();或者在自己创建的线程中Looper.pepare()的时候
1 | private static void prepare(boolean quitAllowed) { |
创建了一个Looper对象并使用sThreadLocal的set方法进行保存
并且这个ThreadLocal在Looper类中是静态的,如下
那就是说,这个静态的ThreadLocal,可以供任何线程访问,但是任意线程中取出来的looper,都只是线程局部变量,都是在副本
所以说,每个线程对应一个looper,
对于ThreadLocal的总结,暂时整理到这里,后续补充 ♨