多线程学习(一):面对多线程,应该考虑的线程安全问题

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。而正当有多个线程同时对某变量执行读和写操作,一般都需要考虑线程同步,否则就可能读到的是脏数据而影响线程安全。

可以采用加锁、ThreadLocal维护的方式控制,达到线程安全


javasynchronized

synchronized加入到成员方法上和synchronized(this)两种写法一样,都是针对对象加锁,而不是方法。

synchronized加到静态方法上,是对类加锁,因为静态方法属于类。

 

数据库的悲观锁

采用数据库机制实现的,如selectvalue from t_table_id where table_name=? for update。数据被锁住之后其他用户将无法查看,知道锁释放(提交或者回滚事务时)。

for update语句只能放到select语句中,因为只有查询时把数据锁住才有意义

 

应用:使用单独表维护主键,便于移植。

由于多个用户同时生成主键,那么就存在访问共享资源的情况,必须使用线程同步。

多线程学习(一):面对多线程,应该考虑的线程安全问题_第1张图片

 

IdGenerator.java

/*
 * Id生成器
 */
publicclass IdGenerator {
 
/**
 * 根据表名产生对应的id
 * @param tblName
 * @return
 * @throws SQLException
 */
 
//        publicstatic synchronized int generate(String tblName){        类加锁
 
//        publicsynchronized int generate(String tblName){                对象加锁
 
//        publicstatic int generate(String tblName) {
//                synchronized(this){
publicstatic int generate(String tblName) {
Stringsql = "select value from t_table_id where table_name=? for update";
Connectionconn = null;
PreparedStatementpstmt = null;
ResultSetrs = null;
intvalue = 0;
 
try{
conn= DbUtil.getConnection();
//开始事务
DbUtil.beginTransaction(conn);
 
pstmt= conn.prepareStatement(sql);
pstmt.setString(1,tblName);
rs= pstmt.executeQuery();
 
if(!rs.next()) {
thrownew RuntimeException();
}
value= rs.getInt("value");
value++;
 
//修改value值,加1
modifyValueField(conn,tblName, value);
 
//提交事务
DbUtil.commitTransaction(conn);
}catch (Exception e) {
e.printStackTrace();
//回滚事务
DbUtil.rollbackTransaction(conn);
thrownew RuntimeException();
}finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
//重置Connection状态
DbUtil.resetConnection(conn);
DbUtil.close(conn);
}
returnvalue;
}
 
/**
 * 修改表value字段
 * @param tblName 表名
 */
privatestatic void modifyValueField(Connection conn, String tblName, int value){
Stringsql = "update t_table_id set value=? where table_name=?";
PreparedStatementpstmt = null;
 
try{
pstmt= conn.prepareStatement(sql);
pstmt.setInt(1,value);
pstmt.setString(2,tblName);
 
pstmt.executeUpdate();
}catch (Exception e) {
e.printStackTrace();
}finally {
DbUtil.close(pstmt);
}
}
 
publicstatic void main(String[] args) {
intid = IdGenerator.generate("t_client");
System.out.println(id);
}
}



ThreadLocal管理

ThreadLocal是线程的本地变量,可以防止线程安全问题,因为它隔离了多线程之间的相关资源,并在同一线程中可以共享这个资源。

 

可以把ThreadLocal理解成map,connectionHolder.get()得到当前线程变量,connectionHolder.set(conn)把connection设置到当前线程变量中。

 

使用ThreadLocal管理当前线程中的Connection

/*
 * 管理Connection
 * 使用ThreadLocal管理当前线程中的Connection
 */
public classConnectionManager {
 
// 本地线程变量
privatestatic ThreadLocal connectionHolder = newThreadLocal();
/**
 * 取得Connection
 * @return
 */
publicstatic Connection getConnection(){
Connectionconn = connectionHolder.get();
if(conn == null) {
conn= DbUtil.getConnection();
connectionHolder.set(conn);
}
returnconn;
}
 
/**
 * 关闭连接,并从当前线程中清除Connection
 */
publicstatic void closeConnection(){
Connectionconn = connectionHolder.get();
if(conn != null) {
try {
conn.close();
//从当前线程中清除Connection
connectionHolder.remove();
}catch (SQLException e) {
e.printStackTrace();
}
}
}
}


处理多线程,保证只拿本线程的Connection,所以ThreadLocal保证线程安全。

同时,事务的开始一般从Service层开始,也就是说从这里建立Connection放入本地线程变量中,而Dao层方法中的参数不再有Connection,只和JDBC关联了,更加符合单一职责原则。

  

无规矩不成方圆,为了维护世界和平,所以有了线程控制,(*^__^*) 嘻嘻……。


你可能感兴趣的:(Java开发)