相同:ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
不同:Synchronized同步机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问;而ThreadLocal采用了“以空间换时间”的方式,每一个线程都提供了一份变量,因此可以同时访问而互不影响。
以时间换空间->即枷锁方式,某个区域代码或变量只有一份节省了内存,但是会形成很多线程等待现象,因此浪费了时间而节省了空间。
以空间换时间->为每一个线程提供一份变量,多开销一些内存,但是呢线程不用等待,可以一起执行而相互之间没有影响。
小结:ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
Synchronized是为了让多线程进行数据共享,而ThreadLocal为了让多线程进行数据隔离
1 public class ConnectionUtil {
2 private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
3 private static Connection initConn = null;
4 static {
5 try {
6 initConn = DriverManager.getConnection("url, name and password");
7 } catch (SQLException e) {
8 e.printStackTrace();
9 }
10 }
11
12 public Connection getConn() {
13 Connection c = tl.get();
14 if(null == c) tl.set(initConn);
15 return tl.get();
16 }
17 }
1 public class ConnectionUtil {
2 private static DBOPool instance=null;
3 public static synchronized Connection getInstance(){
4 if(instance==null)
5 instance=new DBOPool();
6 return instance.getConnection();
7 }
8 }
ThreadLocal的原理是怎么样的?
每个运行的线程都会有一个类型为ThreadLocal.ThreadLocalMap的map,这个map就是用来存储与这个线程绑定的变量,map的key就是ThreadLocal对象,value就是线程正在执行的任务中的某个变量的包装类Entry.
3. ThreadLocal保存变量的生命周期是怎么样的?
ThreadLocal保存变量的生命周期 <=任务的生命周期<=线程的生命周期
4. ThreadLocal的应用及源代码解析 Java代码
- public class UserContextHolder {
- private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<User>();
- public static User get(){
- return userThreadLocal.get();
- }
- public static void set(User user){
- userThreadLocal.set(user);
- }
- }
UserContextHolder的get方法最终会调用ThreadLocal的get方法,ThreadLocal的get方法如下:
Java代码
- public T get() {
- Thread t = Thread.currentThread();//取得当前的thread
- ThreadLocalMap map = getMap(t);//取得当前thread的ThreadLocalMap对象
- if (map != null) { //返回key为当前ThreadLocal的value值
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- return setInitialValue();//返回默认值null
- }
ThreadLocalMap 的key为当前的Threadlocal对象,Threadlocal对象的hashcode实现如下:
Java代码
- 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的值即为 Threadlocal对象的hashcode值,从上面可以知道,
Threadlocal对象的hashcode值是递增的,是HASH_INCREMENT的N倍.这个也和以前遇到的hashcode的生成方式不一样
UserContextHolder的set方法最终会调用ThreadLocal的set方法,ThreadLocal的set方法如下:
Java代码
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);//取得当前线程对应的ThreadLocalMap
- if (map != null) //ThreadLocalMap不空,把value以当前ThreadLocal为key放到ThreadLocalMap 中
- map.set(this, value);
- else //创建一个ThreadLocalMap.并把value放到ThreadLocalMap中
- createMap(t, value);
- }
ThreadLocal还有一个常用的方法remove,如下:
Java代码
- public void remove() {
- ThreadLocalMap m = getMap(Thread.currentThread());
- if (m != null)
- m.remove(this);
- }
Java代码
- /**
- * Remove the entry for key.
- */
- private void remove(ThreadLocal key) {
- Entry[] tab = table;
- int len = tab.length;
- int i = key.threadLocalHashCode & (len-1);
- for (Entry e = tab[i];
- e != null;
- e = tab[i = nextIndex(i, len)]) {
- if (e.get() == key) {
- e.clear();
- expungeStaleEntry(i);
- return;
- }
- }
- }
从上可以看到,调用ThreadLocal的remove方法后,当前存放的Entry会从map中清除
5.ThreadLocal的set(null)和remove方法有什么区别?
set(null)把当前的ThreadLocal为key的值设为了空,避免线程下次再执行其他任务时被使用,但此时这个key对应的Entry值还在,只是Entry.value=null
remove方法会把这个key对应Entry的值设为空
所以从重用和效率的角度来说,set(null)的性能优于remove,在实际的项目中推荐使用set(null)来回收ThreadLocal设置的值.
6. 为什么ThreadLocalMap的Entry是一个weakReference?
使用weakReference,能够在ThreadLocal失去强引用的时候,ThreadLocal对应的Entry能够在下次gc时被回收,回收后的空间能够得到复用,在一定程度下能够避免内存泄露.
7.使用ThreadLocal应该注意什么?
在使用ThreadLocal对象,尽量使用static,不然会使线程的ThreadLocalMap产生太多Entry,从而造成内存泄露
关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。