论坛首页 入门技术论坛

自己写了个连接池

浏览 9300 次
精华帖 (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个线程完全没问题,但是数量一旦上去了就不行了  连接池得不到连接了。

 

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

 

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

   发表时间:2009-07-29  
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回

2、release用来释放连接非常不好,因为很多习惯conn.close()写,导致了连接池中连接收不回来,应该创建连接的时候用Proxy重写Connection的close方法,在其中将true改成false
0 请登录后投票
   发表时间:2009-07-29  
unsid 写道
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回

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


第二个明白了,第一条怎么做的呢,能否详细讲讲
0 请登录后投票
   发表时间: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  没法直接调
0 请登录后投票
   发表时间: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
0 请登录后投票
   发表时间:2009-07-30  
设计的不太好   考虑的太少
觉得用 LinkedList来代替Map 比较好  还是需要用线程
0 请登录后投票
   发表时间: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驱动程序类列表
logfile 日志文件的绝对路径

其它的属性和特定连接池相关,其属性名字前应加上连接池名字:

<poolname>.url 数据库的 JDBC URL
<poolname>.maxconn 允许建立的最大连接数,0表示没有限制
<poolname>.user 用于该连接池的数据库帐号
<poolname>.password 相应的密码

 

 

其中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输出日志记录。


装载和注册所有在drivers属性中指定的JDBC驱动程序由loadDrivers()方法实现。该方法先用StringTokenizer将drivers属性值分割为对应于驱动程序名称的字符串,然后依次装载这些类并创建其实例,最后在 DriverManager中注册


该实例并把它加入到一个私有的向量drivers。向量drivers将用于关闭服务时从DriverManager取消所有JDBC 驱动程序的注册。


init()方法的最后一个任务是调用私有方法createPools()创建连接池对象。createPools()方法先创建所有属性名字的枚举对象(即Enumeration对象,该对象可以想象为一个元素系列,逐次调用其nextElement()方法将顺序返回各元素),然后在其中搜索名字以“.url”结尾的属性。对于每一个符合条件的属性,先提取其连接池名字部分,进而读取所有属于该连接池的属性,最后创建连接池对象并把它保存在实例变量pools中。散列表(Hashtable类 )pools实现连接池名字到连接池对象之间的映射,此处以连接池名字为键,连接池对象为值。


为便于客户程序从指定连接池获得可用连接或将连接返回给连接池,类DBConnectionManager提供了方法getConnection()和freeConnection()。所有这些方法都要求在参数中指定连接池名字,具体的连接获取或返回操作则调用对应的连接池对象完成。


为实现连接池的安全关闭,DBConnectionManager提供了方法release()。在上面我们已经提到,所有DBConnectionManager的客户程序都应该调用静态方法getInstance()以获得该管理器的引用,此调用将增加客户程序计数。


客户程序在关闭时调用release()可以递减该计数。当最后一个客户程序调用release(),递减后的引用计数为0,就可以调用各个连接池的release()方法关闭所有连接了。管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册。

 

 

 

Servlet使用连接池示例

 

 

 

Servlet API所定义的Servlet生命周期类如:

1) 创建并初始化Servlet(init()方法)。
2) 响应客户程序的服务请求(service()方法)。
3) Servlet终止运行,释放所有资源(destroy()方法)。

本例演示连接池应用,上述关键步骤中的相关操作为:

1) 在init(),用实例变量connMgr 保存调用DBManager.getInstance()所返回的引用。
2) 在service(),调用getConnection(),执行数据库操作,用freeConnection()将连接返回给连接池。
3) 在destroy(),调用release()关闭所有连接,释放所有资源。

 

 

 

示例程序清单如下:

 

 

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

0 请登录后投票
   发表时间:2009-08-20  
zxmsdyz 写道
unsid 写道
两个建议:
1、getConnection中,如果池内没有连接则新建一个,新建的又不归池管,那连接池还有什么意义?应该判断如果池内连接全部占用则判断池连接数是否达到最大,如果最大了则阻塞wait,直到有人释放了连接才notify;如果没到,则新建一个连接放在池内并返回

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


第二个明白了,第一条怎么做的呢,能否详细讲讲

 

 

unsid的意思是你做连接池的时候建立一个新的连接。然后把该连接存放起来,下次在连接的时候先判断是否有空闲的连接。如果有直接拿来用

如果没有。在判断是否超过了最大连接数,如果没超过。则重新创建一个新的连接,如超过。则等待

 

0 请登录后投票
   发表时间:2009-08-21  
用 blockingqueue 放conn不错。。
0 请登录后投票
   发表时间:2009-11-16  
lz
你的代码
我测试3000都没问题
??
你改过了?
0 请登录后投票
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics