文档章节

java jdbc深入理解(connection与threadlocal与数据库连接池和事务实)

JackY-Ji
 JackY-Ji
发布于 2017/02/22 21:07
字数 2332
阅读 154
收藏 0

 

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。

 

  1.  // 将线程和连接绑定,保证事务能统一执行  
  2.  成员变量   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>();
  1.  // 将线程和连接绑定,保证事务能统一执行
  2.     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(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

使用场景

  1. To keep state with a thread (user-id, transaction-id, logging-id)
  2. 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);
  }
 }
}

© 著作权归作者所有

共有 人打赏支持
JackY-Ji
粉丝 12
博文 35
码字总数 18318
作品 0
杭州
Java的JDBC数据库连接池实现方法

虽然J2EE程序员一般都有现成的应用服务器所带的JDBC数据库连接池,不过对于开发一般的Java Application、 Applet或者JSP、velocity时,我们可用的JDBC数据库连接池并不多,并且一般性能都不好...

lixun
2012/10/11
0
0
在tomcat中配置JNDI数据源 .

在tomcat5.0中配置数据源(全局数据源、局部数据源),通过连接池机制连接数据库 1. odbc-jdbc桥连 2. 通过加载本地驱动连接 3. 在web应用服务器中设置数据源,通过池接技术连接数据库(加载本...

漠、
2012/05/24
0
0
Hinernate中获得数据库连接池的方式及应用

Hibernate可以与任何一种java应用的运行环境集成。Java应用的运行环境可分为两种。 (1)受管理环境(Managed environment):由容器负责管理各种共享资源(如线程池和数据库连接池),以及管理...

_守望者_
2014/04/22
0
0
Java 连接池 Apache Commons DBCP 2.5.0 发布

Apache Commons DBCP 2.5.0 已发布,这是一个次要版本,主要包含 bug 修复和改进,并要求 Java 8 及以上以支持 JDBC 4.2 。 DBCP(Database Connection Pool)是一个依赖 Jakarta commons-pool...

王练
07/17
0
0
【J2EE系列】JNDI与tomcat连接池

JNDI与tomcat连接池 本章内容 ² JNDI与JNDI服务器 ² 编码实现JNDI目录服务与命名服务 ² 使用Tomcate JNDI服务器 一、JNDI概述 1、什么是JNDI? 英文全称是:Java Naming and Directory Inte...

磊神Ray
2012/02/15
0
1

没有更多内容

加载失败,请刷新页面

加载更多

Mac OS X下Maven的安装与配置

Mac OS X 安装Maven: 下载 Maven, 并解压到某个目录。例如/Users/robbie/apache-maven-3.3.3 打开Terminal,输入以下命令,设置Maven classpath $ vi ~/.bash_profile 添加下列两行代码,之后...

TonyStarkSir
今天
3
0
关于编程,你的练习是不是有效的?

最近由于工作及Solution项目的影响,我在重新学习DDD和领域建模的一些知识。然后,我突然就想到了这个问题,以及我是怎么做的? 对于我来说,提升技能的项目会有四种: 纯兴趣驱动的项目。即...

问题终结者
今天
3
0
打开eclipse出现an error has occurred see the log file

解决方法: 1,打开eclipse安装目录下的eclipse.ini文件; 2,打开的文本文件最后添加一行 --add-modules=ALL-SYSTEM 3,保存重新打开Eclipse。...

任梁荣
昨天
4
0
搞定Northwind示例数据库,无论哪个版本的SQLServer都受用

Northwind数据库 从这里可以找到突破口: http://social.msdn.microsoft.com/Forums/zh-CN/Vsexpressvb/thread/8490a1c6-9018-40c9-aafb-df9f79d29cde 下面是MSDN: http://msdn2.microsoft......

QQZZFT
昨天
1
0
mysql主从同步,安装配置操作

准备 两台mysql服务,我这里准备了如下: 主库:192.168.176.128 从库:192.168.176.131 如何在Linux上安装mysql服务,请看https://blog.csdn.net/qq_18860653/article/details/80250499 操作...

小致dad
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部