锁定老帖子 主题:自己写了个连接池
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-07-28
自己写了个连接池,不过性能不理想,当模拟1000个线程去连接数据库时,会在getConnection这个方法里爆出空指针。
oracle的配置文件:
driver = oracle.jdbc.driver.OracleDriver url = jdbc:oracle:thin:@localhost:1521:DB user = username password = pwd defaultRowPrefetch=50 defaultExecuteBatch=25 poolsize=20 islation=2
连接池: package com.connection; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.Properties; import com.util.PropertiesHelper; import oracle.jdbc.driver.OracleConnection; public class ConnectionPool { private static Map<Connection, Boolean> pool = new HashMap<Connection, Boolean>(); public static DataBase db=null; private static Properties prop; private static int POOLSIZE = 5; public static synchronized void init(String file){ if (pool.size() == 0) { try { prop = readProperties(file); } catch (IOException e) { e.printStackTrace(); } try { Class.forName(prop.getProperty("driver")); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } if (prop.getProperty("poolsize").length() > 0) POOLSIZE = Integer.parseInt(prop.getProperty("poolsize")); for (int i = 0; i < POOLSIZE; i++) { pool.put(newConnection(), true); } } } private static synchronized Connection newConnection() { Connection conn=null; try { String url=prop.getProperty("url"); conn = DriverManager.getConnection(url, prop); conn.setTransactionIsolation(Integer.parseInt(prop.getProperty("islation"))); conn.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } return conn; } public static void closeAll() { for (Connection c : pool.keySet()) { try { c.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static Connection getConnection() { synchronized (pool) { for (Connection conn : pool.keySet()) { if (pool.get(conn) == false) { pool.put(conn, true); return conn; } } } Connection c=newConnection(); return c; } public static void release(Connection conn) { if (conn != null) { synchronized (pool) { if (!pool.keySet().contains(conn)) { try { conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } } else pool.put(conn, false); } } } public static void closePool() { for (Connection conn : pool.keySet()) { try { conn.close(); pool.clear(); } catch (SQLException e) { e.printStackTrace(); } } } private static Properties readProperties(String file) throws IOException { Properties result = new Properties(); InputStream is = ConnectionPool.class.getClassLoader() .getResourceAsStream(file); result.load(is); return result; } }
测试代码:
package com.test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import com.connection.ConnectionPool; import com.dao.BaseDAO; public class Test extends Thread{ private String sql; private Connection c; public Test(Connection c,String sql){ this.c=c; this.sql=sql; } public static void main(String[] args) { ConnectionPool.init("oracle.properties"); for(int i=0;i<100;i++){ String sql="select sysdate from dual"; new Test(ConnectionPool.getConnection(),sql).start(); } } public void run(){ try { System.out.println(c); ResultSet rs=c.createStatement().executeQuery(sql); while(rs.next()){ System.out.println(rs.getString(1)); } } catch (SQLException e) { e.printStackTrace(); }finally{ ConnectionPool.release(c); } } }
模拟100个线程完全没问题,但是数量一旦上去了就不行了 连接池得不到连接了。
我的思路是当你请求一个资源,池会看看池中有没有资源,有的话把这个资源分发给请求,没有的话,生成新的资源同时把该资源放到池中。当请求归还的时候,池会判断池中是否满了,如果满了,那么释放该资源。其中关键的,就是生成新资源,判断是否释放资源。
我自己写是为了多学点知识 一直用别人封装好的连接池 没什么意思,个人写的很烂,欢迎有技术性的建议。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-07-29
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回 2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false |
|
返回顶楼 | |
发表时间:2009-07-29
unsid 写道 两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回 2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false 第二个明白了,第一条怎么做的呢,能否详细讲讲 |
|
返回顶楼 | |
发表时间:2009-07-29
最后修改:2009-07-30
zxmsdyz 写道 unsid 写道 两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回 2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false 第二个明白了,第一条怎么做的呢,能否详细讲讲 private static class ConnectionHandler implements InvocationHandler { /** * 连接是否可用。 */ private boolean enabled; /** * 连接(Connection)对象。 */ private Connection conn; /** * 构造器。 * * @param conn * 连接(Connection)对象。 * */ public ConnectionHandler(Connection conn) { this.conn = conn; enabled = true; } /** * 代理方法,特别对于close方法进行了处理。 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 该连接管理者是否不可用? if (!enabled) // 抛出空指针异常 throw new NullPointerException(); // 是否调用了close方法? if (method.getName() == "close") { if (xx条件) { // 关闭连接。 conn.close(); } } // 正常调用连接的各种方法。 return method.invoke(conn, args); } } 是这样吗? conn的isClose属性是protected 没法直接调 |
|
返回顶楼 | |
发表时间:2009-07-29
最后修改:2009-07-29
自己实现连接池一般应该实现 DataSource 接口,以及用一个实现Connection接口的类, 来包装原始 Connection 实例.
针对你这个类, 说一点, 好像 Connection 的Map 处理有点问题, key是connection , value是Boolean, value 是表示连接是否正被使用吧, 好像处理上有点问题. 然后还有个, 如果 DriverManager.getConnection 产生了异常, 你这里隐藏了这个异常, 当然就返回null了, 这个异常应该抛出来, 方法声明为throws sqlexception. 你现在返回null实际上表示底层的驱动已无法支持这么多的Connection了, 除非你让申请线程等待有连接返回到池中,这样设计就又复杂了. 但不能返回一个null的Connection |
|
返回顶楼 | |
发表时间:2009-07-30
设计的不太好 考虑的太少
觉得用 LinkedList来代替Map 比较好 还是需要用线程 |
|
返回顶楼 | |
发表时间:2009-08-20
最后修改:2009-08-20
MYsql做DEMO演示下给你
首先在SRC下面建立一个db.properties文件.,内容如下:
drivers=com.mysql.jdbc.Driver logfile=D:\\log.txt mysql.url=jdbc:mysql://127.0.0.1:3306/blog?characterEncoding=gb2312 mysql.maxconn=1 mysql.user=root mysql.password=root drivers 以空格分隔的JDBC驱动程序类列表
其中url属性是必需的,而其它属性则是可选的。数据库帐号和密码必须合法。
获得连接处理代码:
public class DBManager { private Connection conn ; private String poolURL = ""; private String driverName =""; private String user="" ; private String pass=""; static private DBManager instance; // 唯一实例 static private int clients; private Vector drivers = new Vector(); private PrintWriter log; private Hashtable pools = new Hashtable(); /** * 返回唯一实例.如果是第一次调用此方法,则创建实例 * * @return DBConnectionManager 唯一实例 */ static synchronized public DBManager getInstance() { if (instance == null) { instance = new DBManager(); } clients++; return instance; } /** * 建构函数私有以防止其它对象创建本类实例 */ private DBManager() { init(); } /** * 将连接对象返回给由名字指定的连接池 * * @param name 在属性文件中定义的连接池名字 * @param con 连接对象 */ public void freeConnection(String name, Connection con) { DBConnectionPool pool = (DBConnectionPool) pools.get(name); if (pool != null) { pool.freeConnection(con); } } /** * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数 * 限制,则创建并返回新连接 * * @param name 在属性文件中定义的连接池名字 * @return Connection 可用连接或null */ public Connection getConnection(String name) { DBConnectionPool pool = (DBConnectionPool) pools.get(name); if (pool != null) { return pool.getConnection(); } return null; } /** * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制, * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接. * * @param name 连接池名字 * @param time 以毫秒计的等待时间 * @return Connection 可用连接或null */ public Connection getConnection(String name, long time) { DBConnectionPool pool = (DBConnectionPool) pools.get(name); if (pool != null) { return pool.getConnection(time); } return null; } public int getClient(){ return clients; } /** * 关闭所有连接,撤销驱动程序的注册 */ public synchronized void release() { // 等待直到最后一个客户程序调用 if (--clients != 0) { return; } Enumeration allPools = pools.elements(); while (allPools.hasMoreElements()) { DBConnectionPool pool = (DBConnectionPool) allPools.nextElement(); pool.release(); } Enumeration allDrivers = drivers.elements(); while (allDrivers.hasMoreElements()) { Driver driver = (Driver) allDrivers.nextElement(); try { DriverManager.deregisterDriver(driver); log("撤销JDBC驱动程序 " + driver.getClass().getName()+"的注册"); } catch (SQLException e) { log(e, "无法撤销下列JDBC驱动程序的注册: " + driver.getClass().getName()); } } } /** * 根据指定属性创建连接池实例. * * @param props 连接池属性 */ private void createPools(Properties props) { Enumeration propNames = props.propertyNames(); while (propNames.hasMoreElements()) { String name = (String) propNames.nextElement(); if (name.endsWith(".url")) { String poolName = name.substring(0, name.lastIndexOf(".")); String url = props.getProperty(poolName + ".url"); if (url == null) { log("没有为连接池" + poolName + "指定URL"); continue; } String user = props.getProperty(poolName + ".user"); String password = props.getProperty(poolName + ".password"); String maxconn = props.getProperty(poolName + ".maxconn", "0"); int max; try { max = Integer.valueOf(maxconn).intValue(); } catch (NumberFormatException e) { log("错误的最大连接数限制: " + maxconn + " .连接池: " + poolName); max = 0; } DBConnectionPool pool = new DBConnectionPool(poolName, url, user, password, max); pools.put(poolName, pool); log("成功创建连接池" + poolName); } } } /** * 读取属性完成初始化 */ private void init() { InputStream is = getClass().getResourceAsStream("/db.properties"); Properties dbProps = new Properties(); try { dbProps.load(is); System.out.println("读取数据成功!"); } catch (Exception e) { System.err.println("不能读取属性文件. " +"请确保db.properties在CLASSPATH指定的路径中"); return; } String logFile = dbProps.getProperty("logfile", "DBManager.log"); System.out.println("日志文件存放位置:"+logFile); try { log = new PrintWriter(new FileWriter(logFile, true), true); }catch (IOException e) { System.err.println("无法打开日志文件: " + logFile); log = new PrintWriter(System.err); } loadDrivers(dbProps); createPools(dbProps); } /** * 装载和注册所有JDBC驱动程序 * * @param props 属性 */ private void loadDrivers(Properties props) { String driverClasses = props.getProperty("drivers"); StringTokenizer st = new StringTokenizer(driverClasses); while (st.hasMoreElements()) { String driverClassName = st.nextToken().trim(); try { Driver driver = (Driver)Class.forName(driverClassName).newInstance(); DriverManager.registerDriver(driver); drivers.addElement(driver); log("成功注册JDBC驱动程序" + driverClassName); } catch (Exception e) { log("无法注册JDBC驱动程序: " + driverClassName + ", 错误: " + e); } } } /** * 将文本信息写入日志文件 */ private void log(String msg) { log.println(new Date().toLocaleString() + ": " + msg); } /** * 将文本信息与异常写入日志文件 */ private void log(Throwable e, String msg) { log.println(new Date() + ": " + msg); e.printStackTrace(log); } /** * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最 * 大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性. */ class DBConnectionPool { private int checkedOut; private Vector freeConnections = new Vector(); private int maxConn; private String name; private String password; private String URL; private String user; /** * 创建新的连接池 * * @param name 连接池名字 * @param URL 数据库的JDBC URL * @param user 数据库帐号,或 null * @param password 密码,或 null * @param maxConn 此连接池允许建立的最大连接数 */ public DBConnectionPool(String name, String URL, String user, String password,int maxConn) { this.name = name; this.URL = URL; this.user = user; this.password = password; this.maxConn = maxConn; } /** * 将不再使用的连接返回给连接池 * * @param con 客户程序释放的连接 */ public synchronized void freeConnection(Connection con) { // 将指定连接加入到向量末尾 freeConnections.addElement(con); checkedOut--; notifyAll(); } /** * 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接 * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之, * 然后递归调用自己以尝试新的可用连接. */ public synchronized Connection getConnection() { Connection con = null; if (freeConnections.size() > 0) { // 获取向量中第一个可用连接 con = (Connection) freeConnections.firstElement(); freeConnections.removeElementAt(0); if (con.isClosed()) { log("从连接池" + name+"删除一个无效连接"); // 递归调用自己,尝试再次获取可用连接 con = getConnection(); } } else if (maxConn == 0 || checkedOut < maxConn) { con = newConnection(); } if (con != null) { checkedOut++; } return con; } /** * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间 * 参见前一个getConnection()方法. * * @param timeout 以毫秒计的等待时间限制 */ public synchronized Connection getConnection(long timeout) { long startTime = new Date().getTime(); Connection con; while ((con = getConnection()) == null) { try { wait(timeout); } catch (InterruptedException e) {} if ((new Date().getTime() - startTime) >= timeout) { // wait()返回的原因是超时 return null; } } return con; } /** * 关闭所有连接 */ public synchronized void release() { Enumeration allConnections = freeConnections.elements(); while (allConnections.hasMoreElements()) { Connection con = (Connection) allConnections.nextElement(); try { con.close(); log("关闭连接池" + name+"中的一个连接"); } catch (SQLException e) { log(e, "无法关闭连接池" + name+"中的连接"); } } freeConnections.removeAllElements(); } /** * 创建新的连接 */ private Connection newConnection() { Connection con = null; try { if (user == null) { con = (Connection) DriverManager.getConnection(URL); } else { con = (Connection) DriverManager.getConnection(URL, user, password); } log("连接池" + name+"创建一个新的连接"); } catch (SQLException e) { log(e, "无法创建下列URL的连接: " + URL); return null; } return con; } } } init()方法在创建属性对象并读取db.properties文件之后,就开始检查logfile属性。如果属性文件中没有指定日志文件,则默认为当前目录下的DBConnectionManager.log文件。如日志文件无法使用,则向System.err输出日志记录。
Servlet使用连接池示例
Servlet API所定义的Servlet生命周期类如:
示例程序清单如下:
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class TestServlet extends HttpServlet { private DBManager connMgr; public void init(ServletConfig conf) throws ServletException { super.init(conf); connMgr = DBManager.getInstance(); } public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); Connection con = connMgr.getConnection("idb"); if (con == null) { out.println("不能获取数据库连接."); return; } ResultSet rs = null; ResultSetMetaData md = null; Statement stmt = null; try { stmt = con.createStatement(); rs = stmt.executeQuery("SELECT * FROM EMPLOYEE"); md = rs.getMetaData(); out.println("<H1>职工数据</H1>"); while (rs.next()) { out.println("<BR>"); for (int i = 1; i < md.getColumnCount(); i++) { out.print(rs.getString(i) + ", "); } } stmt.close(); rs.close(); } catch (SQLException e) { e.printStackTrace(out); } connMgr.freeConnection("idb", con); } public void destroy() { connMgr.release(); super.destroy(); } }
测试下吧O(∩_∩)O |
|
返回顶楼 | |
发表时间:2009-08-20
zxmsdyz 写道
unsid 写道
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回 2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false 第二个明白了,第一条怎么做的呢,能否详细讲讲
unsid的意思是你做连接池的时候建立一个新的连接。然后把该连接存放起来,下次在连接的时候先判断是否有空闲的连接。如果有直接拿来用 如果没有。在判断是否超过了最大连接数,如果没超过。则重新创建一个新的连接,如超过。则等待
|
|
返回顶楼 | |
发表时间:2009-08-21
用 blockingqueue 放conn不错。。
|
|
返回顶楼 | |
发表时间:2009-11-16
lz
你的代码 我测试3000都没问题 ?? 你改过了? |
|
返回顶楼 | |