`
nishijia
  • 浏览: 52247 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

自己写了个连接池

    博客分类:
  • JDBC
阅读更多

自己写了个连接池,不过性能不理想,当模拟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个线程完全没问题,但是数量一旦上去了就不行了  连接池得不到连接了。

 

我的思路是当你请求一个资源,池会看看池中有没有资源,有的话把这个资源分发给请求,没有的话,生成新的资源同时把该资源放到池中。当请求归还的时候,池会判断池中是否满了,如果满了,那么释放该资源。其中关键的,就是生成新资源,判断是否释放资源。

 

我自己写是为了多学点知识  一直用别人封装好的连接池  没什么意思,个人写的很烂,欢迎有技术性的建议。

分享到:
评论
10 楼 nishijia 2009-11-23  
maomiandyou 写道
lz
你的代码
我测试3000都没问题
??
你改过了?


我的代码在oracle 用户多了就有问题 MYSQL 没试过
9 楼 maomiandyou 2009-11-16  
lz
你的代码
我测试3000都没问题
??
你改过了?
8 楼 mathgl 2009-08-21  
用 blockingqueue 放conn不错。。
7 楼 猪也有理想 2009-08-20  
<div class="quote_title">zxmsdyz 写道</div>
<div class="quote_div">
<div class="quote_title">unsid 写道</div>
<div class="quote_div">两个建议:<br>1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回<br><br>2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false</div>
<br><br>第二个明白了,第一条怎么做的呢,能否详细讲讲</div>
<p> </p>
<p> </p>
<p>unsid的意思是你做连接池的时候建立一个新的连接。然后把该连接存放起来,下次在连接的时候先判断是否有空闲的连接。如果有直接拿来用</p>
<p>如果没有。在判断是否超过了最大连接数,如果没超过。则重新创建一个新的连接,如超过。则等待</p>
<p> </p>
6 楼 猪也有理想 2009-08-20  
<p>MYsql做DEMO演示下给你</p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>首先在SRC下面建立一个db.properties文件.,内容如下:</p>
<p> </p>
<pre name="code" class="java">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</pre>
<p><span style="color: #0000ff;">drivers 以空格分隔的JDBC驱动程序类列表<br>logfile 日志文件的绝对路径<br><br>其它的属性和特定连接池相关,其属性名字前应加上连接池名字:<br><br>&lt;poolname&gt;.url 数据库的 JDBC URL <br>&lt;poolname&gt;.maxconn 允许建立的最大连接数,0表示没有限制 <br>&lt;poolname&gt;.user 用于该连接池的数据库帐号<br>&lt;poolname&gt;.password 相应的密码</span></p>
<p> </p>
<p> </p>
<p>其中url属性是必需的,而其它属性则是可选的。数据库帐号和密码必须合法。</p>
<p> </p>
<p> </p>
<p>获得连接处理代码:</p>
<p> </p>
<pre name="code" class="java">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() &gt; 0) {
// 获取向量中第一个可用连接
con = (Connection) freeConnections.firstElement();
freeConnections.removeElementAt(0);
if (con.isClosed()) {
log("从连接池" + name+"删除一个无效连接");
// 递归调用自己,尝试再次获取可用连接
con = getConnection();
}
}
else if (maxConn == 0 || checkedOut &lt; 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) &gt;= 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;
}
}
}
</pre>
 
<p><span style="color: #ff0000;">init()方法在创建属性对象并读取db.properties文件之后,就开始检查logfile属性。如果属性文件中没有指定日志文件,则默认为当前目录下的DBConnectionManager.log文件。如日志文件无法使用,则向System.err输出日志记录。</span></p>
<p><span style="color: #ff0000;"><br>装载和注册所有在drivers属性中指定的JDBC驱动程序由loadDrivers()方法实现。该方法先用StringTokenizer将drivers属性值分割为对应于驱动程序名称的字符串,然后依次装载这些类并创建其实例,最后在 DriverManager中注册</span></p>
<p><span style="color: #ff0000;"><br>该实例并把它加入到一个私有的向量drivers。向量drivers将用于关闭服务时从DriverManager取消所有JDBC 驱动程序的注册。</span></p>
<p><span style="color: #ff0000;"><br>init()方法的最后一个任务是调用私有方法createPools()创建连接池对象。createPools()方法先创建所有属性名字的枚举对象(即Enumeration对象,该对象可以想象为一个元素系列,逐次调用其nextElement()方法将顺序返回各元素),然后在其中搜索名字以“.url”结尾的属性。对于每一个符合条件的属性,先提取其连接池名字部分,进而读取所有属于该连接池的属性,最后创建连接池对象并把它保存在实例变量pools中。散列表(Hashtable类 )pools实现连接池名字到连接池对象之间的映射,此处以连接池名字为键,连接池对象为值。</span></p>
<p><span style="color: #ff0000;"><br>为便于客户程序从指定连接池获得可用连接或将连接返回给连接池,类DBConnectionManager提供了方法getConnection()和freeConnection()。所有这些方法都要求在参数中指定连接池名字,具体的连接获取或返回操作则调用对应的连接池对象完成。</span></p>
<p><span style="color: #ff0000;"><br>为实现连接池的安全关闭,DBConnectionManager提供了方法release()。在上面我们已经提到,所有DBConnectionManager的客户程序都应该调用静态方法getInstance()以获得该管理器的引用,此调用将增加客户程序计数。</span></p>
<p><span style="color: #ff0000;"><br>客户程序在关闭时调用release()可以递减该计数。当最后一个客户程序调用release(),递减后的引用计数为0,就可以调用各个连接池的release()方法关闭所有连接了。管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册。</span></p>
<p> </p>
<p> </p>
<p> </p>
<p>Servlet使用连接池示例</p>
<p> </p>
<p> </p>
<p> </p>
<p>Servlet API所定义的Servlet生命周期类如:<br><br>1) 创建并初始化Servlet(init()方法)。<br>2) 响应客户程序的服务请求(service()方法)。<br>3) Servlet终止运行,释放所有资源(destroy()方法)。<br><br>本例演示连接池应用,上述关键步骤中的相关操作为:<br><br>1) 在init(),用实例变量connMgr 保存调用DBManager.getInstance()所返回的引用。<br>2) 在service(),调用getConnection(),执行数据库操作,用freeConnection()将连接返回给连接池。<br>3) 在destroy(),调用release()关闭所有连接,释放所有资源。</p>
<p> </p>
<p> </p>
<p> </p>
<p>示例程序清单如下:</p>
<p> </p>
<p> </p>
<pre name="code" class="java">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("&lt;H1&gt;职工数据&lt;/H1&gt;");
while (rs.next()) {
out.println("&lt;BR&gt;");
for (int i = 1; i &lt; 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();
}
}</pre>
 
<p> </p>
<p> </p>
<p>测试下吧O(∩_∩)O</p>
5 楼 nishijia 2009-07-30  
设计的不太好   考虑的太少
觉得用 LinkedList来代替Map 比较好  还是需要用线程
4 楼 srdrm 2009-07-29  
自己实现连接池一般应该实现 DataSource 接口,以及用一个实现Connection接口的类, 来包装原始 Connection 实例.

针对你这个类, 说一点, 好像 Connection 的Map 处理有点问题, key是connection , value是Boolean, value 是表示连接是否正被使用吧, 好像处理上有点问题.

然后还有个, 如果 DriverManager.getConnection 产生了异常, 你这里隐藏了这个异常, 当然就返回null了, 这个异常应该抛出来, 方法声明为throws sqlexception. 你现在返回null实际上表示底层的驱动已无法支持这么多的Connection了, 除非你让申请线程等待有连接返回到池中,这样设计就又复杂了. 但不能返回一个null的Connection
3 楼 nishijia 2009-07-29  
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  没法直接调
2 楼 zxmsdyz 2009-07-29  
unsid 写道
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回

2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false


第二个明白了,第一条怎么做的呢,能否详细讲讲
1 楼 unsid 2009-07-29  
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回

2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false

相关推荐

Global site tag (gtag.js) - Google Analytics