下面的代码是我在研究数据库连接池化和事物控制时写的,看似没有什么问题的代码,其实隐藏了一个十分严重的问题。
1.
package com.example.ds;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
//import java.util.concurrent.ConcurrentHashMap;
public class ConnectFactory {
private HashMap<String, UserConn> userConnPools;
private static int MAX_CONN_NUM = 15;
public ArrayList<Connection> idleConnPools;
private ConnectFactory() {
userConnPools = new HashMap<String, UserConn>();
idleConnPools = new ArrayList<Connection>();
try {
Class.forName("org.gjt.mm.mysql.Driver").newInstance();
} catch (Exception e) {
e.printStackTrace();
System.out.println("==>注册驱动程序错误!");
}
for (int i = 0; i < MAX_CONN_NUM; i++) {
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/jpetstore?user=mysql_user&password=sa&useunicode=true&characterencoding=utf8");
idleConnPools.add(conn);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("==>初始化idleConnPools错误!");
}
}
System.out.println("==>注册Connection成功!");
}
// private ConnPools connPools;
private static final ConnectFactory _instance = new ConnectFactory();
public static ConnectFactory getInstance() {
return _instance;
}
public Connection getConn() {
String threadId = com.example.thread.ThreadValue.getThreadId();
System.out.println("==>获取线程号:" + threadId);
UserConn userConn = null;
try {
userConn = userConnPools.get(threadId);
} catch (Exception e) {
// TODO: handle exception
System.out.println("==>获取UserConn错误!");
}
if (userConn == null) {
if (idleConnPools.size() > 0) {
Connection conn = idleConnPools.remove(idleConnPools.size() - 1);
userConn = new UserConn(conn);
userConnPools.put(threadId, userConn);
} else {
throw new RuntimeException("所有可以使用的连接均在使用中!");
}
}
System.out.println("分配Conn成功,线程号:" + threadId);
return userConn.getConn();
}
public void closeConn() {
String threadId = com.example.thread.ThreadValue.getThreadId();
UserConn userConn = userConnPools.remove(threadId);
if (userConn != null) {
Connection conn = userConn.getConn();
idleConnPools.add(conn);
userConn.closeConn();
} else {
throw new RuntimeException("返回不需要使用的连接时错误!");
}
}
public List getIdelConnPools() {
return idleConnPools;
}
public Collection<UserConn> getUserConnPools() {
return userConnPools.values();
}
}
2.
package com.example.ds;
import java.sql.Connection;
public class UserConn {
private static int count_num = 0;
private Connection conn;
private int flowId = 0;
private boolean beUsed;
private String threadId;
public boolean isBeUsed() {
return beUsed;
}
public String getThreadId() {
return threadId;
}
public UserConn(Connection conn1) {
flowId = count_num++;
beUsed = false;
conn = conn1;
}
public Connection getConn() {
beUsed = true;
return conn;
}
public void closeConn() {
beUsed = false;
}
public String toString() {
return (new StringBuffer()).append("==>UserConn=[flowId=").append(flowId).append(",beUsed=").append(beUsed).append("]").toString();
}
}
当多个线程同时访问ConnectFactory时,ConnectFactory的getConn和closeConn方法在处理ArrayList和HashMap时就面临着同步的问题。即两个线程同时获取到了一个idle的Conn,而userConnPools里面就会有一个重复的Conn,导致事物上面处理的混乱。