Maison > Questions et réponses > le corps du texte
我启动了十个线程去测试一个业务方法,但有时候会报出连接已经关闭,无法继续操作的异常。直接上代码了:
首先是测试代码:
public class Test {
public static void main(String[] args){
// ProductService productService = new ProductServiceImpl();
// productService.updateProductPrice(1, 3000);
for(int i=0;i<10;i++){
ProductService productService = new ProductServiceImpl();
MyThread myThread = new MyThread(productService);
myThread.start();
}
}
}
class MyThread extends Thread{
private ProductService productService;
public MyThread(ProductService productService){
this.productService = productService;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
productService.updateProductPrice(1,3000);
}
}
业务代码:
public class ProductServiceImpl implements ProductService{
private static final String UPDATE_PRODUCT_SQL = "update product set price = ? where id = ?";
private static final String INSERT_LOG_SQL = "insert into log (created, description) values (?, ?)";
public void updateProductPrice(long productId, int price) {
Connection conn = null;
try {
// 获取连接
conn = DBUtil.getConnection();
System.out.println(Thread.currentThread().getName()+"---->"+conn.toString());
conn.setAutoCommit(false); // 关闭自动提交事务(开启事务)
// 执行操作
updateProduct(conn, UPDATE_PRODUCT_SQL, productId, price); // 更新产品
insertLog(conn, INSERT_LOG_SQL, "Create product."); // 插入日志
// 提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭连接
DBUtil.closeConnection(conn);
}
}
private void updateProduct(Connection conn, String updateProductSQL, long productId, int productPrice) throws Exception {
PreparedStatement pstmt = conn.prepareStatement(updateProductSQL);
pstmt.setInt(1, productPrice);
pstmt.setLong(2, productId);
int rows = pstmt.executeUpdate();
if (rows != 0) {
System.out.println("Update product success!");
}
}
private void insertLog(Connection conn, String insertLogSQL, String logDescription) throws Exception {
PreparedStatement pstmt = conn.prepareStatement(insertLogSQL);
pstmt.setString(1, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
pstmt.setString(2, logDescription);
int rows = pstmt.executeUpdate();
if (rows != 0) {
System.out.println("Insert log success!");
}
}
}
数据库操作类:
public class DBUtil {
private static final String driver = "com.mysql.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:3306/demo";
private static final String username = "root";
private static final String password = "root";
// 定义一个数据库连接
private static Connection conn = null;
// 获取连接
public static Connection getConnection() {
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
// 关闭连接
public static void closeConnection(Connection conn) {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中一次得到的运行结果:
伊谢尔伦2017-04-18 10:30:56
Le problème réside dans cette ligne de code dans DBUtil :
private static Connection conn = null;
La variable conn est une variable statique globale et est partagée par tous les threads.
Un cas extrême :
Lorsque le thread A s'exécute sur conn = DriverManager.getConnection(...), conn est la base de données à laquelle se connecte
Le thread B s'exécute sur conn = DriverManager.getConnection(...) Lorsque conn est réaffecté en tant que connexion à la base de données b
À l'heure actuelle, puisque conn est partagé par tous les threads, la valeur de conn pour le thread A et le thread B est la connexion à la base de données b. Cela a pour conséquence que tous les threads partagent une connexion à la base de données. Lorsqu'un thread ferme la connexion, d'autres threads lèvent des exceptions.
De plus, puisqu'une connexion à la base de données b est partagée à la fin, la connexion à la base de données a ne peut pas être fermée, provoquant une fuite de mémoire.
巴扎黑2017-04-18 10:30:56
Il faut dire qu'il n'y a aucune garantie qu'une connexion différente sera renvoyée à chaque fois
Après tout, vous l'avez écrite en tant que variable membre
天蓬老师2017-04-18 10:30:56
Pour utiliser Pool, initialisez-en plusieurs et attribuez-en un à chaque connexion.
public class ConnectionPool {
private Vector<Connection> pool;
private int poolSize = 100;
private static ConnectionPool instance = null;
Connection conn = null;
private ConnectionPool() {
pool = new Vector<Connection>(poolSize);
for (int i = 0; i < poolSize; i++) {
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url, username, password);
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/* 返回连接到连接池 */
public synchronized void release() {
pool.add(conn);
}
/* 返回连接池中的一个数据库连接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}
}