ThreadLocal解决了什么问题?内部源码是怎么样的?
作用:
为每个线程创建一个副本
实现在线程的上下文传递同一个对象,比如connection
第一个问题:证明ThreadLocal为每个线程创建一个变量副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class ThreadLocalTest { private static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void main (String[] args) throws InterruptedException { Task task = new Task(); new Thread(task).start(); Thread.sleep(10 ); new Thread(task).start(); } static class Task implements Runnable { @Override public void run () { Long result = threadLocal.get(); if (result == null ){ threadLocal.set(System.currentTimeMillis()); System.out.println(Thread.currentThread().getName()+"->" +threadLocal.get()); } } } }
输出的结果是不同的
问题二:为什么可以给每个线程保存一个不同的副本
那我们来分析源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Long result = 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 ) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
我们需要结合set方法的源码分析,才可以更好理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 threadLocal.set(System.currentTimeMillis()); public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); }
所以,我们得到结论:
每个线程都会有对应的map,map来保存键值对。
问题三:ThreadLocal这种特性,在实际开发中解决了什么问题?
比如:hibernate管理session,mybatis管理sqlsession,其内部都是采用ThreadLocal来实现的。
前提知识:不管是什么框架,最本质的操作都是基于JDBC,当我们需要跟数据库打交道的时候,都需要有一个connection。
那么,当我们需要在业务层实现事务控制时,该如何达到这个效果?
我们构建下代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class UserService { private UserDao userDao = new UserDao(); private LogDao logDao = new LogDao(); public void add () { userDao.add(); logDao.add(); } } public class UserDao { public void add () { System.out.println("UserDao add。。。" ); } } public class LogDao { public void add () { System.out.println("LogDao add。。。" ); } }
如果代码按上面的方式来管理connection,我们还可以保证service的事务控制吗?
这是不行的,假设第一个dao操作成功了,那么它就提交事务了,而第二个dao操作失败了,它回滚了事务,但不会影响到第一个dao的事务,因为上面这么写是两个独立的事务
那么怎么解决。
上面的根源就是两个dao操作的是不同的connection
所以,我们保证是同个connection即可
1 2 3 4 5 6 7 public void add () { Connection connection = new Connection(); userDao.add(connection); logDao.add(connection); }
上面的方式代码不够优雅
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class ConnectionUtils { private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); public static Connection getConnection () { Connection connection = threadLocal.get(); if (connection == null ){ connection = new Connection(); threadLocal.set(connection); } return connection; } } public class UserDao { public void add () { System.out.println("UserDao add。。。" ); Connection connection = ConnectionUtils.getConnection(); System.out.println("UserDao->" +connection); } }
到此,我们可以保证两个dao操作的是同一个connection