ThreadLocal及Connection(jdbc连接池 事务绑定)

本文深入探讨了数据库连接池的工作原理,特别是如何利用ThreadLocal确保每个线程拥有独立的数据库连接,从而提高多线程环境下的事务处理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;  
    }
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);
  }
 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值