ThreadLocal的介绍与运用( 四 )

? (2) AccountService类的修改:不需要传递connection对象
package com.itheima.transfer.service;import com.itheima.transfer.dao.AccountDao;import com.itheima.transfer.utils.JdbcUtils;import java.sql.Connection;public class AccountService {public boolean transfer(String outUser, String inUser, int money) {AccountDao ad = new AccountDao();try {Connection conn = JdbcUtils.getConnection();//开启事务conn.setAutoCommit(false);// 转出 : 这里不需要传参了 !ad.out(outUser, money);// 模拟转账过程中的异常//int i = 1 / 0;// 转入ad.in(inUser, money);//事务提交JdbcUtils.commitAndClose();} catch (Exception e) {e.printStackTrace();//事务回滚JdbcUtils.rollbackAndClose();return false;}return true;}}? (3) AccountDao类的修改:照常使用
package com.itheima.transfer.dao;import com.itheima.transfer.utils.JdbcUtils;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.SQLException;public class AccountDao {public void out(String outUser, int money) throws SQLException {String sql = "update account set money = money - ? where name = ?";Connection conn = JdbcUtils.getConnection();PreparedStatement pstm = conn.prepareStatement(sql);pstm.setInt(1,money);pstm.setString(2,outUser);pstm.executeUpdate();//照常使用//JdbcUtils.release(pstm,conn);JdbcUtils.release(pstm);}public void in(String inUser, int money) throws SQLException {String sql = "update account set money = money + ? where name = ?";Connection conn = JdbcUtils.getConnection();PreparedStatement pstm = conn.prepareStatement(sql);pstm.setInt(1,money);pstm.setString(2,inUser);pstm.executeUpdate();//JdbcUtils.release(pstm,conn);JdbcUtils.release(pstm);}}2.3.2 ThreadLocal方案的好处从上述的案例中我们可以看到,在一些特定场景下,ThreadLocal方案有两个突出的优势:

  1. 传递数据 : 保存每个线程绑定的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题
  2. 线程隔离 : 各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失
3. ThreadLocal的内部结构? 通过以上的学习,我们对ThreadLocal的作用有了一定的认识 。现在我们一起来看一下ThreadLocal的内部结构,探究它能够实现线程数据隔离的原理 。
3.1常见的误解? 通常,如果我们不去看源代码的话,我猜ThreadLocal是这样子设计的:每个ThreadLocal类都创建一个Map,然后用线程的ID threadID作为Mapkey,要存储的局部变量作为Mapvalue , 这样就能达到各个线程的局部变量隔离的效果 。这是最简单的设计方法,JDK最早期的ThreadLocal就是这样设计的 。
3.2核心结构? 但是,JDK后面优化了设计方案,现时JDK8 ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap哈希表,这个哈希表的keyThreadLocal实例本身,value才是真正要存储的值Object
? (1) 每个Thread线程内部都有一个Map (ThreadLocalMap)? (2) Map里面存储ThreadLocal对象(key)和线程的变量副本(value)? (3)Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值 。? (4)对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰 。
ThreadLocal的介绍与运用

文章插图
3.3 这样设计的好处? 这个设计与我们一开始说的设计刚好相反,这样设计有如下两个优势:
(1) 这样设计之后每个Map存储的Entry数量就会变少,因为之前的存储数量由Thread的数量决定 , 现在是由ThreadLocal的数量决定 。
(2) 当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用 。
4. ThreadLocal的核心方法源码? 基于ThreadLocal的内部结构 , 我们继续探究一下ThreadLocal的核心方法源码,更深入的了解其操作原理 。
除了构造之外 ,  ThreadLocal对外暴露的方法有以下4个:
方法声明描述protected T initialValue()返回当前线程局部变量的初始值public void set( T value)设置当前线程绑定的局部变量public T get()获取当前线程绑定的局部变量public void remove()移除当前线程绑定的局部变量其实get,set和remove逻辑是比较相似的,我们要研究清楚其中一个,其他也就明白了 。
4.1 get方法(1 ) 源码和对应的中文注释
/*** 返回当前线程中保存ThreadLocal的值* 如果当前线程没有此ThreadLocal变量,* 则它会通过调用{@link #initialValue} 方法进行初始化值** @return 返回当前线程对应此ThreadLocal的值*/public T get() {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null) {// 以当前的ThreadLocal 为 key , 调用getEntry获取对应的存储实体eThreadLocalMap.Entry e = map.getEntry(this);// 找到对应的存储实体 eif (e != null) {@SuppressWarnings("unchecked")// 获取存储实体 e 对应的 value值// 即为我们想要的当前线程对应此ThreadLocal的值T result = (T)e.value;return result;}}// 如果map不存在,则证明此线程没有维护的ThreadLocalMap对象// 调用setInitialValue进行初始化return setInitialValue();}/*** set的变样实现,用于初始化值initialValue,* 用于代替防止用户重写set()方法** @return the initial value 初始化后的值*/private T setInitialValue() {// 调用initialValue获取初始化的值T value = https://www.huyubaike.com/biancheng/initialValue();// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null)// 存在则调用map.set设置此实体entrymap.set(this, value);else// 1)当前线程Thread 不存在ThreadLocalMap对象// 2)则调用createMap进行ThreadLocalMap对象的初始化// 3)并将此实体entry作为第一个值存放至ThreadLocalMap中createMap(t, value);// 返回设置的值valuereturn value;}/*** 获取当前线程Thread对应维护的ThreadLocalMap** @paramt the current thread 当前线程* @return the map 对应维护的ThreadLocalMap*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;} /***创建当前线程Thread对应维护的ThreadLocalMap** @param t 当前线程* @param firstValue 存放到map中第一个entry的值*/ void createMap(Thread t, T firstValue) {//这里的this是调用此方法的threadLocalt.threadLocals = new ThreadLocalMap(this, firstValue);}

推荐阅读