本文以数据库操作Dao为例进行描述ThreadLocal的使用,如下是一个反例:
package com.daxin.threadlocal.dao; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; public class UserDaoErrorDemo { // ①一个非线程安全的变量 private Connection conn; /** * addUser在并发时候关于事务的提交容易出现错乱 */ public void addUser() { try { // ②引用非线程安全变量 conn.setAutoCommit(false); Statement stat = conn.createStatement(); stat.executeQuery("sql"); conn.commit(); } catch (SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } } }
上面存在线程安全问题,在多个线程进行事务提交时候会出现错乱。因此可以通过如下方案:
package com.daxin.threadlocal.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; /** * 模拟数据库连接管理器 * * @author liuguangxin * */ class ConnectionManager { static String driverName = ""; static String url = ""; static String user = ""; static String password = ""; public static Connection getConnection() { try { return DriverManager.getConnection(url, user, password); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } } /** * * @author liuguangxin * */ public class UserDao { // ①使用ThreadLocal保存Connection变量 // 由于Dao通产刚在系统中是单例的,因此可以被多个线程共享,因此connThreadLocal也是被多个线程共享的。 private ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); public Connection getConnection() { // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection, 并将其保存到线程本地变量中。 if (connThreadLocal.get() == null) { Connection conn = ConnectionManager.getConnection(); connThreadLocal.set(conn); return conn; } else { // ③直接返回线程本地变量 return connThreadLocal.get(); } } public void addUser(String userName, String passWord) { Connection conn = getConnection(); Statement stmt = null; try { conn.setAutoCommit(false); stmt = conn.createStatement(); // 伪代码 stmt.executeQuery("insert into user ...."); conn.commit(); } catch (SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }