个人理解,请大家擦亮眼睛,有错必究,否则误人子弟!
首先要知道,线程安全,通俗来讲就是:一个线程调用此变量不会影响另一个线程
静态变量:线程非安全(因为静态变量位于方法区,所有对象共享内存,一旦静态变量被修改,其他对象均受影响,故线程非安全)
成员变量:非单例线程安全
单例模式线程非安全(因为成员变量为对象私有,存放在堆中,调用该对象的所有线程共享此变量)
局部变量:线程安全(每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。)
为什么引入ThreadLocal
ThreadLocal是JDK5.0以后才出现的
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
在BaseDao中protectedConnection conn;即数据库连接是非单例的(数据库参数等的初始化是单例),一个线程中,每次new一个dao,然后dao.xxx方法里会有this.openConnection每次打开的都是新的connection,但是connection资源是非常有限的,所以就需要把一个线程中的connection共享给所有的dao
想到设计成protectedstaticConnection conn;达到了节省connection资源的目的,但是由于静态变量是线程不安全的,多线程情况下会共享,可能会发生某个线程还在进行操作,而另一个线程就将connection关闭或者提交,这绝对不可以。
于是又想到用synchronized锁住,但是又带来一个问题一个线程在使用connect进行数据库操作的时候,其他线程只有等待,执行效率会很低。
至此,我们需要达成两个目的:
1. 线程只打开一个connection资源(线程中的一切业务都共享connection)
2. 每个线程打开的connection不互相影响,各操作各的
即把connection这个变量本地化给这个线程,每个线程都操作本地的connection
鉴于以上情景,ThreadLocal就诞生了,ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,线程之间互不影响。
举例说明:
事物:添加班级和学生在同一事物,保证班级添加成功后再添加班级,否则学生没有外键约束出错。
传统方案:默认每个dao每次openConnection的时候都会打开一个新的connection,除非在事物中,通过传递的方式进行共享。
传统的jdbc再事物中需要把stuDao开启事物(开启事物中就openConnection)
然后把connection传递给classDao,即classDao.setConn(stuDao.getConn());然后classDao和stuDao这两个业务就在一条流水线上,共享同一个connection连接。
ThreadLocal:默认所有dao共享一个connection,不管在不在事物中,每次openConnection的时候是去ThreadLocal中取,而不是打开新的。
有了ThreadLocal以后,就可以把需要共享的connection放到ThreadLocal中(底层是Map),然后dao需要用的时候去取就可以
另外别把连接池和它们搞混,连接池是存放connection资源的池子,有了连接池,线程openConnection的时候就不用真正的去打开了,而是直接从池子中取出一个,用完放回即可
传统方案无ThreadLocal:
Ui:
StuJdbcBiz biz = new StuJdbcBiz();
biz.addStudentAndClass();
Biz:
public void addStudentAndClass()throws Exception{
StuJdbcDao stuDao = new StuJdbcDao();
ClassJdbcDao classDao = new ClassJdbcDao();
try {
stuDao.beginTransaction();//开启事物,打开connection
classDao.setConn(stuDao.getConn());//传递connection
classDao.addClass();//添加班级
stuDao.addStudent();//添加学生
stuDao.commit();
} catch (Exceptione) {
stuDao.rollback();
e.printStackTrace();
}
}
开启事物
public void beginTransaction()throws Exception {
this.openConnection();//打开数据库连接
if (this.conn != null){
this.conn.setAutoCommit(false);
}
}
打开数据库连接
public void openConnection()throws Exception {
if (this.conn == null || this.conn.isClosed()){
DbInfo dbinfo = DbInfo.instance();
Class.forName(dbinfo.getDbDriver());
conn =DriverManager.getConnection(dbinfo.getDbURL(),dbinfo.getUsername(),dbinfo.getPassword());
} else {
return;//已经打开的话直接跳出
}
}
Dao:
添加班级:
public void addClass()throws Exception{
String sql = "insertintotclass values(?,?)";
System.out.println(this.getConn().hashCode());
this.openConnection();//conn不为空,直接return
System.out.println(this.getConn().hashCode());
PreparedStatement ps = this.conn.prepareStatement(sql);
ps.setString(1, "java999");
ps.setString(2, "c999");
ps.executeUpdate();
ps.close();
}
添加学生:
public void addStudent()throws Exception{
String sql = "insertintotstudent values(?,?,?,?)";
this.openConnection();//connection不为空,直接return
PreparedStatement ps = this.conn.prepareStatement(sql);
ps.setString(1, "s999");
ps.setString(2, null);
ps.setString(3, "llw999");
ps.setInt(4, 199);
ps.executeUpdate();
ps.close();
}
Hibernate中ThreadLocal方案:
Ui:
StuBiz biz = new StuBiz();
biz.addStudentAndClass();
Biz:
public void addStudentAndClass()throws Exception{
StuDao stuDao = new StuDao();
ClassDao classDao = new ClassDao();
try {
stuDao.beginTransaction();//开启事物,打开connection
classDao.addClass();//添加班级
stuDao.addStudent();//添加学生
stuDao.commit();
} catch (Exceptione) {
stuDao.rollback();
e.printStackTrace();
}
}
打开事物
public void beginTransaction()throws Exception{
this.openConnection();//打开数据库连接
transaction=this.session.beginTransaction();
//底层还是setAutoComment()为false
}
打开数据库连接
public void openConnection()throws Exception{
session=HibernateSessionFactory.getSession();//得到session
}
从ThreadLocal中取得session
public static Session getSession() throwsHibernateException{
Session session =(Session) threadLocal.get();
//先从threadLocal中得到该线程session变量副本
if (session == null ||!session.isOpen()) {
//isOpen()检查session是否打开,未打开返回true
if (sessionFactory == null){//检查工厂是否创建
rebuildSessionFactory();
}
session = (sessionFactory != null)? sessionFactory.openSession(): null;//真正的打开session
threadLocal.set(session);//将session变量放入threadLocal中
}
return session;
}
Dao
添加班级
public void addClass()throws Exception{
Tclass tclass = new Tclass();
tclass.setCno("c111");
tclass.setCname("java111");
this.openConnection();
this.session.save(tclass);
}
添加学生
public void addStudent()throws Exception{
Tstudent stu = new Tstudent();
stu.setSno("s111");
stu.setSname("llw111");
this.openConnection();
this.session.save(stu);
}