ThreadLocal
一、ThreadLocal概述
- 本地线程变量
- 本质上是一种利用线程的执行由程序的上游向下游传递信息的机制
- Thread对象内置了一个Map来存取消息 , 但是这个 Map外界无法直接操作 , 需要通过ThreadLocal来实现Thread中Map进行数据的存取
- ThreadLocal就是用来实现Thread的Map存取过程的类
- 每一个线程保存各自自己的对象 , 后续获取来使用 , 每个线程都有自己的对象自然不会有线程安全的问题
二、ThreadLocal方法
- ThreadLocal t1 = new ThreadLocal:获取当前线程 , 并获取线程中的map对象
- t1.set(obj) : 向当前线程的map中存储t1:obj键值对
- t1.get():得到当前线程中的map , 从map中查找t1对应的值并返回
- t1.remove() : 移除map中的键值对
三、基于数据库事务、ThreadLocal的实际案例
- 场景:添加商品
- 分析: 添加新的商品时 , 需要先判断商品种类表中是否有对应的种类 , 如果有这个种类执行商品信息插入数据库 , 如果没有这商品种类 , 则在商品种类表中添加商品种类后 , 将商品信息插入数据库中 。 但是需要注意 , 在数据库中不允许出现有商品种类但是没有对应商品的情况 , 所以查询商品种类表、添加商品种类 、 添加商品 , 是一组操作 , 要么都执行成功 , 要么都不执行成功 。
- 应用技术: 数据库事务操作查看详情 、 ThreadLocal机制
- 实现思路分析:
- 当添加商品操作时 , 将查询商品种类表、添加商品种类 、 添加商品放到一组事务中 , 也就是说这三个操作必须要使用同一个连接对象conn , 在service层中统一开启事务 ,执行完三个操作后统一提交事务后再统一释放conn对象 。但是这种情况下操作数据库的方法只能由service层调用 , 如果在Service层中创建Connection conn对象 , 再通过方法参数的传递将conn传递给dao层 , 虽然可以实现功能 , 但是违反了JavaEE经典三层架构的原理 , 在Service层中竟然出现了Dao层特有的对象 , 发生了耦合 。
- 如果这种耦合不可避免, 那么我们需要尽量将耦合管理起来 , 把service层中的事务操作抽取到一个工具类中 , 将Conn设置为static共享资源 , 一组事务内的所有数据库操作共享同一个 conn , 这样就实现了功能的需求 。
- 但是在并发情况下两个用户同时用一个conn对象 , 势必会发生事务的混乱 , 比如发生后来的用户提交了先来用户未操作玩的事务 … 。 这时候需要引入ThreadLocal机制保证每一个用户都有自己的conn ,从而实现了事务之间互不干涉的目的。
- 需要注意的是:
- 由于一组事务中的所有数据库操作用的都是一个conn , 所以在事务中的每一步操作完成后都不能释放conn , 必须等一组事务中的所有数据库操作完成后统一释放conn
代码:
事务管理工具 public class TransManage { private TransManage(){} private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); //获取连接 public static Connection getConn(){ return tl.get(); } //释放资源 public static void releaase(){ Connection conn = tl.get(); MySqlUtils.close(null, null, conn); tl.remove(); } //开启事务 public static void startTrans(){ try { Connection conn = MySqlUtils.getConn(); conn.setAutoCommit(false); tl.set(conn); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } //提交事务 public static void commitTrans(){ try { tl.get().commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //回滚事务 public static void rollbackTrans(){ try { tl.get().rollback(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } service层执行一组事务 private ProdDao dao =BaseFactory.getBase().getInstance(ProdDao.class); public void addProd(Prod prod) { try{ //开启事务 TransManage.startTrans(); //1.根据商品种类查询 Prod_category category = dao.findPordCategoryByName(prod.getCategory().getCategory()); //2. 处理商品种类 int c_id = 0; if(category == null){ //商品种类不存在, 添加商品种类 Prod_category pc = new Prod_category(); pc.setCategory(prod.getCategory().getCategory()); dao.addprodcategory(pc); //并获得商品种类的编号保存在商品信息中 Prod_category findpc = dao.findPordCategoryByName(prod.getCategory().getCategory()); c_id = findpc.getId(); }else{ //如果找到 , 就查询商品种类的编号保存在商品信息中 c_id = category.getId(); } //3. 将商品存入数据库中 prod.getCategory().setId(c_id); dao.addprod(prod); //没有发生异常 , 则提交事务 TransManage.commitTrans(); }catch(Exception e){ //如果发生异常 , 则回滚 TransManage.rollbackTrans(); throw new RuntimeException(e); }finally{ //释放资源 TransManage.releaase(); } }