源码解析访问:https://blog.csdn.net/qq_31129841/article/details/105551924(多线程:线程之间的共享和协作(二))
ThreadLocal和synchronized都用于解决多线程并发访问。
ThreadLocal与Synchronized本质区别:
①ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的共享。
②synchronized是利用锁的机制,使变量或代码块在某一时刻仅仅能被一个线程访问。
例:
Spring的事务就借助了ThreadLocal类。 Spring会从数据库连接池中获得一个connection,然后会把connection放进ThreadLocal中,也就和线程绑定了,事务需要提交或者回滚,只要从ThreadLocal中拿到connection进行操作。
为何Spring的事务要借助ThreadLocal类?
以JDBC为例,正常的事务代码如下:
dbc=newDataBaseConnection();//第 1 行
Connectioncon=dbc.getConnection();//第 2 行
con.setAutoCommit(false);//第 3 行
con.executeUpdate(...);//第 4 行
con.executeUpdate(...);//第 5 行
con.executeUpdate(...);//第 6 行
con.commit();//第 7 行
大致划分为三部分:
①事务准备阶段:1~3行;
②业务处理阶段:4~6行;
③事务提交阶段:第7行。
不管开启事务还是执行具体的SQL都需要一个具体的数据库连接。
我们开发商业项目一般采用三层结构,如果控制事务的代码都放在DAO(DataAccessObject)对象中,在DAO对象的每个方法当中去打开事务和关闭事务,当Service对象在调用DAO时,如果只调用一个DAO,我们这样实现则效果不错,但往往我们的Service会调用一系列的DAO对数据库进行多次操作,这个时候我们就无法控制事务的边界了,因为实际应用当中,Service调用的DAO的个数是不确定的,可根据需求而变化,而且还可能出现Service调用Service的情况。
如果不使用ThreadLocal,代码大概就是这个样子:
public void serviceOrder() {
Connection conn = null;
try {
conn = getConnection();
conn.setAutoCommit(false);
Dao1 dao1 = new Dao1(conn);
dao1.doSomething();
Dao2 dao2 = new Dao2(conn);
dao2.doSomething();
Dao3 dao3 = new Dao3(conn);
dao3.doSomething();
conn.commit();
} catch (Exception e) {
throw e;
}
}
private Connection conn = null;
public Dao1(Connection conn) {
this.conn = conn;
}
public void doSomething() {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
pstmt.execute();
} catch (Exception e) {
throw e;
}
}
需要注意的是:如果让三个DAO使用同一个数据源连接呢? 就必须为每个DAO传递同一个数据库连接,要么就是在DAO实例化的时候作为构造方法的参数传递,要么在每个DAO的实例方法中作为方法的参数传递。这两种方式对Spring框架或者码农来说都不合适。为了让这个数据库连接可以跨阶段传递,又不显示的进行参数传递,就必须使用别的方式方法。
Web容器中,每个完整的请求周期会由一个线程来处理,因此,如果能将一些参数绑定到线程的话,就可以实现在软件架构中跨层次的参数共享(是隐式共享)。而Java中恰好提供了绑定的方法——》使用ThreadLocal。
只要将一个数据库连接放入ThreadLocal中,当前线程执行时只要有使用数据库连接的地方,就从ThreadLocal获取。