java多线程之ThreadLocal
来源:互联网 发布:淘宝家具安装服务 编辑:程序博客网 时间:2024/05/16 14:05
在线程同步中,我们可以使用锁机制,或者通过CAS。但是还有一种方法就是ThreadLocal。这里先举一个生活中的例子,
比如,让100个人填写个人信息表,如果只有一支笔的话,那么大家就得挨个填写,为了让每个人都能完成的填写,我们就需要
保证大家不能哄抢这一支笔,否则谁也填不玩,这时候可能大家可以想到利用锁机制来控制这支笔。其实从另外一种角度出发,
我们可以每人发一支笔让他们填写信息表。如果锁是第一种思路的话,那么ThreadLocal就是第二种思路。接下来先看ThreadLocal
的简单使用:
下面先来看一个简单的示例:
public class ThreadLocalDemo {private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static class ParseDate implements Runnable{int i = 0;public ParseDate(int i){this.i = i;}@Overridepublic void run() {try{Date t = sdf.parse("2017-03-34 17:10:"+i%60);System.out.println(i+":"+t);}catch(ParseException e){e.printStackTrace();}}}public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(10);//这里使用到线程池,创建含有十个线程的线程池for(int i =0;i<1000;i++){es.execute(new ParseDate(i));}}}上述代码在多线程中使用SimpleDateFormat来解析字符串类型的日志。如果你执行上面代码,一般来说,你很可能得到一些异常
这里截取一部分:
Exception in thread "pool-1-thread-116" java.lang.NumberFormatException: For input string: ""at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)at java.lang.Long.parseLong(Long.java:453)出现这些问题的原因,是SimpleDateFormat.parse()方法并不是线程安全的。因此在线程池中共享这个对象必然导致错误。
下面我们看看ThreadLocal的解决方法:
public class ThreadLocalDemo {static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>();public static class ParseDate implements Runnable{int i = 0;public ParseDate(int i){this.i = i;}@Overridepublic void run() {try{if(t1.get()==null){t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));}Date t = t1.get().parse("2017-03-34 17:10:"+i%60);System.out.println(i+":"+t);}catch(ParseException e){e.printStackTrace();}}}public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(10);//这里使用到线程池,创建含有十个线程的线程池for(int i =0;i<1000;i++){es.execute(new ParseDate(i));}}}
结果截取一部分如下:
992:Mon Apr 03 17:10:32 CST 2017991:Mon Apr 03 17:10:31 CST 2017989:Mon Apr 03 17:10:29 CST 2017987:Mon Apr 03 17:10:27 CST 2017986:Mon Apr 03 17:10:26 CST 2017984:Mon Apr 03 17:10:24 CST 2017983:Mon Apr 03 17:10:23 CST 2017999:Mon Apr 03 17:10:39 CST 2017在threadlocal的解决方案中10-14行,如果当前线程不持有SimpleDateFormat对象实例。那么就新建一个并把它设置到当前线程中,
如果已经持有,则直接使用。
ThreadLocal的实现原理:
我们需要关注的是ThreadLocal中的set()方法和get()方法。从set()方法开始说,首先看看set()方法的源码:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }我们剖析一下,我们在调用set方法时,首先获得当前线程对象,然后通过getMap()拿到当前线程的ThreadLocalMap,
(这里的ThreadLocalMap其实就是线程中的一个成员变量)并将值设入hreadLocalMap中。其实ThreadLocalMap的
实现使用了弱引用,这个概念在《深入理解java虚拟机》有提过,这里解释一下,类似Object obj = new Object() 这类引用表示强引用,
而弱引用的强度顾名思义比强引用弱,带来的结果就是在GC的时候,无论当前内存是否够用,都会回收掉被弱引用关联的对象。
ThreadLocalMap之所以采用弱引用其实好处在于垃圾回收。你看上面的代码可以发现,我们设置在ThreadLocal中的数据,
其实就是写入到ThreadLocalMap这个map中,这里的key表示的就是ThreadLocal这个对象实例,value就是我们需要写入的值。
说到这,我们再提一下ThreadLocalMap采用弱引用为什么好处在于垃圾回收。我们一但在我们程序中将ThreadLocal设置为null的话,
那么ThreadLocal这个强引用没了,而ThreadLocalMap中的key也就是ThreadLocal实例又都是弱引用,自然里面的value就会被垃圾回收。
这其实也是清理ThreadLocal的一种措施。后面还会讲到其他清理措施。
下面再来看看get()方法的源码:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }首先,get()方法先取得当前线程的ThreadLocalMap对象。然后通过将自己作为key取得内部的实际数据。
在了解了ThreadLocal的内部实现后,我们自然后会引出一个问题。那就是这些变量时维护在Thread类内部的,
这也意味着只要线程不退出,对象的引用就会一直存在。这里先说明一下,线程在退出时会做一些清理工作,其中
就包括ThreadLocalMap的清理。
如果我们使用线程池,那就意味着当前线程未必会退出。如果这样,将一些打打的对象设置到ThreadLocal中
(它实际是保存在当前线程持有的ThreadLocalMap中),最后可能会造成内存泄漏问题。如果你设置在ThreadLocal
中的对象不再使用了,你需要清理它。有以下两种方法:
- 通过ThreadLocal.remove()
- 就是前面说的设置ThreadLocal的实例为null
- Java多线程之ThreadLocal
- Java多线程之ThreadLocal
- JAVA多线程之ThreadLocal
- Java多线程之 ThreadLocal
- java多线程之ThreadLocal
- Java多线程之----ThreadLocal
- Java多线程之ThreadLocal
- Java多线程之ThreadLocal
- java多线程之ThreadLocal
- java多线程之ThreadLocal
- Java 多线程之--ThreadLocal 简介
- (四)java多线程之ThreadLocal
- java多线程并发控制之ThreadLocal
- java多线程之——ThreadLocal
- (8)Java多线程之ThreadLocal
- java多线程并发控制之ThreadLocal
- Java多线程学习之ThreadLocal源码分析
- java多线程之ThreadLocal源码分析
- Leetcode刷题日记
- sql复杂查询语句总结
- openwrt-mt7688 修改默认的WIFI SSID
- 【Angular2】遍历嵌套实体生成数组
- (lintcode)第15题 全排列(没有重复数字)
- java多线程之ThreadLocal
- 使用Aspose组件将WORD、PDF、PPT转为图片
- HDU.2111 Saving HDU(贪心)
- mysql优化
- .proto 转换成 .lua
- List、Set、Map判断两个对象相等的标准
- Makefile简易教程
- 深入了解ConcurrentHashMap的底层实现
- U-boot从tftp服务器启动,挂载到NFS根文件系统