java多线程之ThreadLocal
2017-09-01 15:56
211 查看
在线程同步中,我们可以使用锁机制,或者通过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;
}
@Override
public 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;
}
@Override
public 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 2017
991:Mon Apr 03 17:10:31 CST 2017
989:Mon Apr 03 17:10:29 CST 2017
987:Mon Apr 03 17:10:27 CST 2017
986:Mon Apr 03 17:10:26 CST 2017
984:Mon Apr 03 17:10:24 CST 2017
983:Mon Apr 03 17:10:23 CST 2017
999: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
比如,让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;
}
@Override
public 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;
}
@Override
public 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 2017
991:Mon Apr 03 17:10:31 CST 2017
989:Mon Apr 03 17:10:29 CST 2017
987:Mon Apr 03 17:10:27 CST 2017
986:Mon Apr 03 17:10:26 CST 2017
984:Mon Apr 03 17:10:24 CST 2017
983:Mon Apr 03 17:10:23 CST 2017
999: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高级进阶关于java多线程的应用 ThreadLocal多线程实例详解
- Java多线程编程--(4)ThreadLocal的使用
- java多线程——ThreadLocal
- Java多线程——ThreadLocal
- 译 -- Java 并发编程(多线程)三 | Semaphore | ThreadLocal | synchronized
- 【Java多线程与并发库】05 线程范围内共享变量ThreadLocal
- Java多线程10:ThreadLocal的作用及使用
- Java线程和多线程(七)——ThreadLocal
- Java 学习笔记16:用ThreadLocal解决多线程安全问题
- Java多线程——2 ThreadLocal
- JAVA多线程-线程间通信(五)-类ThreadLocal的使用
- Java多线程之 ThreadLocal
- 浅谈Java中的ThreadLocal的多线程应用问题
- [Java多线程]-ThreadLocal源码及原理的深入分析
- java多线程(6)---ThreadLocal
- java核心知识点学习----多线程间的数据共享和对象独立,ThreadLocal详解
- java多线程模式ThreadLocal原理简述及其使用详解