Java多线程(10)——ThreadLocal
来源:互联网 发布:第八届云计算大会ppt 编辑:程序博客网 时间:2024/05/21 06:17
ThreadLocal是Java框架中经常使用的工具。对于这个知识点,网上博文毛毛多,但有不少都存在一些错误。不过知识就是这样不断建立,发现问题,打破重建螺旋上升的过程。在此记录一下我的认识过程:
第一层级:初识
ThreadLocal顾名思义,线程局部变量。
因此ThreadLocal是线程独占而非处理多线程同步问题的。这在一些博文中有误解。
ThreadLocal就是在使用该对象的没一个线程中创建独立的副本,多个线程彼此之间是隔离的,实际上操作的是不同对象。
示例
代码
public class ThreadLocalDemo{ private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ public Integer initialValue(){ return 0; } }; public int getNextNum(){ seqNum.set(seqNum.get() + 1); return seqNum.get(); } public static void main(String[] args){ ThreadLocalDemo sn = new ThreadLocalDemo(); TestClient t1 = new TestClient(sn); TestClient t2 = new TestClient(sn); TestClient t3 = new TestClient(sn); TestClient t4 = new TestClient(sn); t1.start(); t2.start(); t3.start(); t4.start(); } private static class TestClient extends Thread { private ThreadLocalDemo sn; public TestClient(ThreadLocalDemo sn){ this.sn = sn; } public void run(){ for(int i=0;i<3;i++){ System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn[" + sn.getNextNum() + "]"); } } }}
输出结果:
thread[Thread-0] --> sn[1]thread[Thread-0] --> sn[2]thread[Thread-2] --> sn[1]thread[Thread-1] --> sn[1]thread[Thread-1] --> sn[2]thread[Thread-1] --> sn[3]thread[Thread-3] --> sn[1]thread[Thread-3] --> sn[2]thread[Thread-2] --> sn[2]thread[Thread-0] --> sn[3]thread[Thread-2] --> sn[3]thread[Thread-3] --> sn[3]
多提一句,可以发现代码没有引入任何其他类库,原来ThreadLocal和Thread都是java.lang包下的类,已经自动加载了。
源码分析
ThreadLocal的实现在java.lang.ThreadLocal类中;
ThreadLocal有一个内部类ThreadLocalMap;
Thread类中有一个ThreadLoacl.ThreadLocalMap域threadLocals。
很多博文混淆不清就是因为不能理清楚ThreadLocal,ThreadLocalMap,Thread之间的关系。
ThreadLocal
1,ThreadLocal是个泛型类:
public class ThreadLocal<T> {
2,ThreadLocal的域:
ThreadLocal只有三个域,
private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode = new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;
配合一个方法:
private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT);}
做的事情就是实现一个ThreadLocal的哈希值threadLocalHashCode,这个哈希值在ThreadLocalMap中用到。
3,ThreadLocal的方法:
Thread一共四个基本方法:
(1) void set(Object value)设置当前线程的线程局部变量的值。 (2) public Object get()该方法返回当前线程所对应的线程局部变量。 (3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 (4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。
基本方法都是通过封装ThreadLocalMap的方法实现的,在看过了ThreadLocal源码后更好理解。
ThreadLocalMap
1, ThreadLocalMap没有继承任何Map,而是单独实现了一个Map功能。如果理解HashMap源码的话再看ThreadLocalMap会比较轻松。
2, ThreadLocalMap元素存储:
ThreadLocalMap定义了一个内部类Entry来存储元素:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }}
Entry继承了 WeakReference,实现一个键值对,键是ThreadLocal,值是一个Object。
有了Entry后,ThreadLocalMap持有一个Entry数组table来存储元素,和HashMap类似。
private Entry[] table;
3,ThreadLocalMap元素操作:
作为一个Map,自然要有get,set操作,在这里就用到了上面ThreadLocal中说到的threadLocalHashCode。以get为例:
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e);}
可知ThreadLocalMap是使用threadLocalHashCode来做元素定位的。
Thread
Thread类和ThreadLocal相关的就是它的一个域:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
源码已经说明了,这个域的管理是ThreadLocal来做的,而Thread不操作它,唯一与之发生关系的就是线程退出方法exit():
private void exit() { if (group != null) { group.threadTerminated(this); group = null; } /* Aggressively null out all reference fields: see bug 4006245 */ target = null; /* Speed the release of some of these resources */ threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }
把threadLocals置为空,这在下节的内存泄露问题还将提到。
再看ThreadLocal
之前我们看过Thread的大框知道了ThreadLocal的四个基本方法,也说明了基本方法是调用ThreadLocalMap实现的,那么接下来看如何实现的:
首先看获取和创建ThreadLocalMap的方法:
getMap
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
可知,是根据线程,获取线程的threadLocals域,但threadLocals域默认为空,所以有了创建方法createMap:
/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
创建方法,发现在创建同时还放入了一个元素firstValue,由此可以推测,基本方法应该要利用getMap和createMap配合条件来实现的,让我们一探究竟:
get
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
我们看到其中根据线程操作threadLocalMap,再根据ThreadLocal本身操作对象的过程。
总结:
一个ThreadLocal
实例一方面本身代表着一个键,代表着一个指定类型<T>
的变量,另一方面拥有着根据键,操作线程存储和获取这个变量的方法。
每个线程Thread都拥有一个ThreadLocalMap
表,里面可以以键值对形式存不同类型的变量,其中键是ThreadLocal
类型,值是ThreadLocal<T>
的T类型。
概念之所以比较容易混淆,是因为在ThreadLocal中放了太多的东西,如果我来实现,将ThreadLocal和ThreadLocalMap分离,再将ThreadLocal的类型定义功能和线程操作功能分开,会更好理解。但写在一起可以做到更好的封装,应该会有利于安全性吧。
内存泄露问题
在上面提到过,每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap。Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。 每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。
但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用。只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread,Map, value将全部被GC回收。
所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露,这时就要根据要实现的功能妥善处理了。
在深入
看似理解,但纸上得来终觉浅,遇到问题还是会发现模棱两可。对于遇到的问题,记录如下,适时更新。
1,ThreadLocalMap存储的时间键值对,那么键是什么,值是什么?
答:键是ThreadLocal实例,值是对应线程的变量副本。
详细说明:ThreadLocalMap把ThreadLocal实例作为键,而在实际方法中,是获取ThreadLocal的哈希值threadLocalHashCode。在复制一次源码:
private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode = new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT);}
仔细分析,threadLocalHashCode是final的,在ThreadLocal实例初始化是通过方法nextHashCode()计算得来,初始化后不可变。
而nextHashCode是static,是静态变量,再看nextHashCode()方法,也是static的,操作nextHashCode自增固定长度。所以每多一个ThreadLocal实例,nextHashCode就会增加,保证了每两个ThreadLocal之间的threadLocalHashCode都不样,这样把它作为键值对的键就可行了。
思考一下threadLocalHashCode有没有可能重复,答案是很难,当ThreadLocal实例很多,可能超过int值范围,这样可能会转回来,使两个哈希值相同。但这样的情况微乎其微,首先这需要有相当多的ThreadLocal实例,其次每次递增的步长HASH_INCREMENT可能也是有讲究的(我猜测,不确定)。所以此事不足为虑。
- Java多线程(10)——ThreadLocal
- Java多线程——ThreadLocal
- java多线程——ThreadLocal
- Java 多线程(九)——ThreadLocal类
- Java线程和多线程(七)——ThreadLocal
- Java 多线程(九)——ThreadLocal类
- Java多线程——2 ThreadLocal
- Java多线程——ThreadLocal类
- java多线程之——ThreadLocal
- java多线程(一) ThreadLocal
- 跟我学Java多线程——ThreadLocal
- Java——多线程总结、ThreadLocal/Volatile/synchronized/Atomic关键字
- 跟我学Java多线程——ThreadLocal
- Java多线程总结(4)— 线程范围内数据操作的隔离及ThreadLocal类
- Java多线程(六)、ThreadLocal类
- Java多线程(六)、ThreadLocal类
- Java多线程数据隔离(ThreadLocal)
- (8)Java多线程之ThreadLocal
- Python中静态方法 类方法 实例方法的不同
- Linux间进程通信 管道
- 解决同时安装多个版本jdk,cmd验证和path不一致的问题
- Neo4j CQL -(8)- WHERE子句
- 大数据数组查找最大的100个数据
- Java多线程(10)——ThreadLocal
- 了解linux下的系统调用
- lua提供的string方法
- 时间复杂度和空间复杂度
- CPTTRN1
- 细谈select函数(C语言)
- Apply SAP Note
- Java 反射机制学习笔记
- 奥威汽贸行业数据分析软件Power-BI-新增来店分析