ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal并不能替代同步机制,两者面向的问题领域不同。
1:同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;
2:而threadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享变量,这样当然不需要对多个线程进行同步了。
import java.util.Random; public class ThreadSocpeShareData { static ThreadLocal<Integer> t = new ThreadLocal<Integer>(); public static void main(String[] args) { for(int i=0;i<3;i++){ new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() +" has put "+ data); t.set(data); MyThreadScopeData.getInstance().setName("name" + data); MyThreadScopeData.getInstance().setAge("age"+data); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ int data = t.get(); MyThreadScopeData myData = MyThreadScopeData.getInstance(); System.out.println("A " + Thread.currentThread().getName() +" "+ data + myData.getAge() + myData.getName() /*ms.getName()*/); } } static class B{ public void get(){ int data = t.get(); System.out.println("B " + Thread.currentThread().getName()+ " "+ data); } } } class MyThreadScopeData{ private MyThreadScopeData(){} private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>(); public static MyThreadScopeData getInstance(){ MyThreadScopeData instance = map.get(); if(instance == null){ instance = new MyThreadScopeData(); map.set(instance); } return instance; } private String name; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
事实上,我们向ThreadLocal中set的变量不是由ThreadLocal来存储的,而是Thread线程对象自身保存。当用户调用ThreadLocal对象的set(Object o)时,该方法则通过Thread.currentThread()获取当前线程,将变量存入Thread中的一个Map内,而Map的Key就是当前的ThreadLocal实例。请看源码,这是最主要的两个函数,能看出ThreadLocal与Thread的调用关系:
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 getMap(Thread t) { return t.threadLocals; } 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 getMap(Thread t) { return t.threadLocals; }
具体可以看看里面的源码,不过有一点是可以证实的,就是Threadlocal中 创建的线程副本,可以不调用remove来做清理工作,因为jvm在发现线程的调佣不再使用时,会进行自动的垃圾回收操作,我以前写程序在使用Threadlocal时却是经常的进行使用完成之后的清理工作。(防止占用内存,如果创建的副本数量不是太多的话,可以让虚拟机自动来清除)