java jdbc深入理解(connection与threadlocal与数据库连接池和事务实)
1.jdbc 连接数据库,就这样子
Class.forName("com.mysql.jdbc.Driver");
java.sql.Connection conn = DriverManager.getConnection(jdbcUrl);
2. 通过传入 jdbc url 用 Drivermanager.getConnection(jdbcurl) 连接数据库,
注意:一次 Drivermanager.getConnection(jdbcurl) 获得只是一个 connection,并不能满足高并发情况。因为 connection 不是线程安全的,一个 connection 对应的是一个事物。
3. 所以数据库连接池,是多次 Drivermanager.getConnection(jdbcurl),获取多个 connection 放入 hashmap 中。
4. 每次获得 connection 都需要浪费 cpu 资源和内存资源,是很浪费资源的。所以诞生了数据库连接池。
5. 数据库连接池部分源码:
注意 pool.getConnection(),都是先从 threadlocal 里面拿的,如果 threadlocal 里面有,则用,保证线程里的多个 dao 操作,用的是同一个 connection,以保证事务。
如果新线程,则将新的 connection 放在 threadlocal 里,再 get 给到线程。
着重看以下几个方法,说明数据库连接池,是将 connection 放进 threadlocal 里的,以保证每个线程从连接池中获得的都是线程自己的 connection。
- // 将线程和连接绑定,保证事务能统一执行
- 成员变量 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
// 获得当前连接 public Connection getCurrentConnecton(){ // 默认线程里面取 Connection conn = threadLocal.get(); if(!isValid(conn)){conn = getConnection(); } return conn; }
// 获得连接 public synchronized Connection getConnection() { Connection conn = null; try { // 判断是否超过最大连接数限制 if(contActive < this.dbBean.getMaxActiveConnections()){if (freeConnection.size() > 0) {conn = freeConnection.get(0); if (conn != null) {threadLocal.set(conn); } freeConnection.remove(0); } else {conn = newConnection(); }}else{ // 继续获得连接,直到从新获得连接 wait(this.dbBean.getConnTimeOut()); conn = getConnection(); } if (isValid(conn)) { activeConnection.add(conn); contActive ++; } } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return conn; }</pre>
public synchronized void releaseConn(Connection conn) throws SQLException {if (isValid(conn)&& !(freeConnection.size() > dbBean.getMaxConnections())) {freeConnection.add(conn); activeConnection.remove(conn); contActive --; threadLocal.remove(); // 唤醒所有正待等待的线程,去抢连接 notifyAll();} }然后再着重理解此段话首先,LZ 是概念上的错误. 什么是线程池, 什么是 ThreadLocal??? 线程池, 为避免不必要的创建, 销毁 connection 而存在的, 其中包括活动, 等待, 最小等属性,cop3,proxy 连接池都可以配置这些玩意;至于为什么要用 ThreadLocal 呢? 这个和连接池无关, 我认为更多的是和程序本身相关, 为了更清楚的说明, 我举个例子
servlet 中获取一个连接. 首先,servlet 是线程安全的吗?
class MyServlet extends HttpServlet{
private Connection conn;
}
ok, 遗憾的告诉你, 这个 conn 并不是安全的, 所有请求这个 servlet 的连接, 使用的都是一个 Connection, 这个就是致命的了. 多个人使用同一个连接, 算上延迟啥的, 天知道数据会成什么样.
因此我们要保证 Connection 对每个请求都是唯一的. 这个时候就可以用到 ThreadLocal 了, 保证每个线程都有自己的连接.
改为 private ThreadLocal<Connection> ct = new ThreadLocal<Connnection>();
然后从连接池获取 Connection,set 到 ct 中, 再 get 就行了, 至于得到的是哪个 Connection 就是连接池的问题了, 你也管不到.
Hibernate 的数据库连接池就是将 connection 放进 threadlocal 实现的!!!Hibernate 的数据库连接池就是将 connection 放进 threadlocal 实现的!!!Hibernate 的数据库连接池就是将 connection 放进 threadlocal 实现的!!!public class ConnectionPool implements IConnectionPool { // 连接池配置属性 private DBbean dbBean; private boolean isActive = false; // 连接池活动状态 private int contActive = 0;// 记录创建的总的连接数// 空闲连接 private List<Connection> freeConnection = new Vector<Connection>(); // 活动连接 private List<Connection> activeConnection = new Vector<Connection>();
- // 将线程和连接绑定,保证事务能统一执行
- private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
public ConnectionPool(DBbean dbBean) {super(); this.dbBean = dbBean; init(); cheackPool();}// 初始化 public void init() { try { Class.forName(dbBean.getDriverName()); for (int i = 0; i < dbBean.getInitConnections(); i++) { Connection conn; conn = newConnection(); // 初始化最小连接数 if (conn != null) { freeConnection.add(conn); contActive++; } } isActive = true; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } // 获得当前连接 public Connection getCurrentConnecton(){ // 默认线程里面取 Connection conn = threadLocal.get(); if(!isValid(conn)){ conn = getConnection(); } return conn; } // 获得连接 public synchronized Connection getConnection() { Connection conn = null; try { // 判断是否超过最大连接数限制 if(contActive < this.dbBean.getMaxActiveConnections()){ if (freeConnection.size() > 0) { conn = freeConnection.get(0); if (conn != null) { threadLocal.set(conn); } freeConnection.remove(0); } else { conn = newConnection(); } }else{ // 继续获得连接,直到从新获得连接 wait(this.dbBean.getConnTimeOut()); conn = getConnection(); } if (isValid(conn)) { activeConnection.add(conn); contActive ++; } } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return conn; } // 获得新连接 private synchronized Connection newConnection() throws ClassNotFoundException, SQLException { Connection conn = null; if (dbBean != null) { Class.forName(dbBean.getDriverName()); conn = DriverManager.getConnection(dbBean.getUrl(), dbBean.getUserName(), dbBean.getPassword()); } return conn; } // 释放连接 public synchronized void releaseConn(Connection conn) throws SQLException { if (isValid(conn)&& !(freeConnection.size() > dbBean.getMaxConnections())) { freeConnection.add(conn); activeConnection.remove(conn); contActive --; threadLocal.remove(); // 唤醒所有正待等待的线程,去抢连接 notifyAll(); } } // 判断连接是否可用 private boolean isValid(Connection conn) { try { if (conn == null || conn.isClosed()) { return false; } } catch (SQLException e) { e.printStackTrace(); } return true; } // 销毁连接池 public synchronized void destroy() { for (Connection conn : freeConnection) { try { if (isValid(conn)) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } for (Connection conn : activeConnection) { try { if (isValid(conn)) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } isActive = false; contActive = 0; } // 连接池状态 @Override public boolean isActive() { return isActive; } // 定时检查连接池情况 @Override public void cheackPool() { if(dbBean.isCheakPool()){ new Timer().schedule(new TimerTask() { @Override public void run() { // 1.对线程里面的连接状态 // 2.连接池最小 最大连接数 // 3.其他状态进行检查,因为这里还需要写几个线程管理的类,暂时就不添加了 System.out.println("空线池连接数:"+freeConnection.size()); System.out.println("活动连接数::"+activeConnection.size()); System.out.println("总的连接数:"+contActive); } },dbBean.getLazyCheck(),dbBean.getPeriodCheck()); } }
}
Why ThreadLocal?
无论如何,要编写一个多线程安全 (Thread-safe) 的程序是困难的,为了让线程共享资源,必须小心地对共享资源进行同步,同步带来一定的效能延迟,而另一方面,在处理同步的时候,又要注意对象的锁定与释放,避免产生死结,种种因素都使得编写多线程程序变得困难。
尝试从另一个角度来思考多线程共享资源的问题,既然共享资源这么困难,那么就干脆不要共享,何不为每个线程创造一个资源的复本。将每一个线程存取数据的行为加以隔离,实现的方法就是给予每个线程一个特定空间来保管该线程所独享的资源
什么是 ThreadLocal?
顾名思义它是 local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。
使用场景
- To keep state with a thread (user-id, transaction-id, logging-id)
- To cache objects which you need frequently
ThreadLocal 类 实现线程范围的共享变量
它主要由四个方法组成 initialValue(),get(),set(T),remove(),其中值得注意的是 initialValue(),该方法是一个 protected 的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第 1 次调用 get()或者 set(Object) 时才执行,并且仅执行 1 次。ThreadLocal 中的确实实现直接返回一个 null:
ThreadLocal 的原理
ThreadLocal 是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在 ThreadLocal 类中有一个 Map,用于存储每一个线程的变量的副本。比如下面的示例实现:
public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
}public void set(Object newValue)
{
values.put(Thread.currentThread(), newValue);
}public Object initialValue()
{
return null;
}
}ThreadLocal 的使用
使用方法一:
hibernate的文档时看到了关于使 ThreadLocal 管理多线程访问的部分。具体代码如下
1. public static final ThreadLocal session = new ThreadLocal();
2. public static Session currentSession() {
3. Session s = (Session)session.get();
4. //open a new session,if this session has none
5. if(s == null){
6. s = sessionFactory.openSession();
7. session.set(s);
8. }
return s;
9. }
我们逐行分析
1。 初始化一个 ThreadLocal 对象,ThreadLocal 有三个成员方法 get()、set()、initialvalue()。
如果不初始化 initialvalue,则 initialvalue 返回 null。
3。 session 的 get 根据当前线程返回其对应的线程内部变量,也就是我们需要的 net.sf.hibernate.Session(相当于对应每个数据库连接). 多线程情况下共享数据库链接是不安全的。ThreadLocal 保证了每个线程都有自己的 s(数据库连接)。
5。如果是该线程初次访问,自然,s(数据库连接)会是 null,接着创建一个 Session,具体就是行 6。
6。创建一个数据库连接实例 s
7。保存该数据库连接 s 到 ThreadLocal 中。
8。如果当前线程已经访问过数据库了,则从 session 中 get() 就可以获取该线程上次获取过的连接实例。使用方法二
当要给线程初始化一个特殊值时,需要自己实现 ThreadLocal 的子类并重写该方法,通常使用一个内部匿名类对 ThreadLocal 进行子类化,EasyDBO 中创建 jdbc 连接上下文就是这样做的:
public class JDBCContext{
private static Logger logger = Logger.getLogger(JDBCContext.class);
private DataSource ds;
protected Connection connection;
private boolean isValid = true;
private static ThreadLocal jdbcContext;
private JDBCContext(DataSource ds){
this.ds = ds;
createConnection();
}
public static JDBCContext getJdbcContext(javax.sql.DataSource ds)
{
if(jdbcContext==null)jdbcContext=new JDBCContextThreadLocal(ds);
JDBCContext context = (JDBCContext) jdbcContext.get();
if (context == null) {
context = new JDBCContext(ds);
}
return context;
}private static class JDBCContextThreadLocal extends ThreadLocal {
public javax.sql.DataSource ds;
public JDBCContextThreadLocal(javax.sql.DataSource ds)
{
this.ds=ds;
}
protected synchronized Object initialValue() {
return new JDBCContext(ds);
}
}
}使用单例模式,不同的线程调用 getJdbcContext() 获得自己的 jdbcContext,都是通过 JDBCContextThreadLocal 内置子类来获得 JDBCContext 对象的线程局部变量
本文部分转自 http://blog.csdn.net/wenzhihui_2010/article/details/8985575