单例模式保证一个类仅有一个实例 并且提供一个全局的访问点
在java中保证一个类仅有一个实例 就需要先考虑实例化类对象有那些方式?
常见的有:通过new关键字 反射技术 克隆
为了防止通过new关键字和反射技术获得类实例 那么可以通过私有构造方法
防止克隆获得类对象的话 可以将类什么为final 类
常见的两种单例:
懒汉式:
/** * 懒汉式 */ final class SingletonObj{ private static SingletonObj instance = null ; private SingletonObj(){} public static SingletonObj getInstance(){ if(instance==null){ synchronized(SingletonObj.class){ instance = new SingletonObj() ; return instance ; } }else{ return instance ; } } }
饿汉式 :
/** * 饿汉式 */ final class SingletonObj02{ private static SingletonObj02 instance = new SingletonObj02() ; private SingletonObj02(){} public static SingletonObj02 getInstance(){ return instance ; } }
那么实际开发中什么对象设置为单例呢?
实例化很耗费性能的对象 线程重量级的对象一般都可以设置为单例对象 例如:数据源 hibernate中的会话工厂SessionFactory 等
那么下面以数据源的创建为例子 来说明一个单例的实际用法:
说到数据源的时候常见的有两种:c3p0 dbcp 这两种数据源
下面采用c3p0数据源做例子:
需要导入jar包
c3p0-0.9.2-pre2.jar
mchange-commons-java-0.2.1.jar
常见一个DataSource类 提供一个获取c3p0的数据源对象:
package org.lkl.singleton; import com.mchange.v2.c3p0.ComboPooledDataSource; public class DataSource { public static ComboPooledDataSource getDataSource() throws Exception{ ComboPooledDataSource cpds = new ComboPooledDataSource() ; cpds.setDriverClass("oracle.jdbc.driver.OracleDriver"); cpds.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:orcl"); cpds.setUser("scott"); cpds.setPassword("tiger"); cpds.setMinPoolSize(5); cpds.setAcquireIncrement(5); cpds.setMaxPoolSize(20); cpds.setInitialPoolSize(3) ; System.out.println("获取数据源"); return cpds ; } }
通过上述类可以获得数据源 但如何让这个数据源是一个单例呢?
创建一个DBManager类 让改类为一个单例 在类的私有构造方法中实例化一个数据源对象 因为该类构造方法私有了 也就只能通过该类提供的全局访问点来获取DBManager的类对象 其对应的构造方法
只能调用一次 从而数据源也就只会初始化一次 代码如下:
package org.lkl.singleton; import java.sql.Connection; import java.sql.SQLException; import com.mchange.v2.c3p0.ComboPooledDataSource; /** * 数据库管理对象 */ public class DBManager { private static DBManager instance = new DBManager() ; private ComboPooledDataSource cpds = null ; /** * 在私有的构造中获取数据源 只会执行一次 */ private DBManager(){ try { cpds = DataSource.getDataSource() ; } catch (Exception e) { e.printStackTrace(); } } /** * 全局访问点 */ public static DBManager getInstance(){ return instance ; } /** * 获取数据库连接 * @throws SQLException */ public Connection getConnection() throws SQLException{ System.out.println(" 获取数据库连接"); return cpds.getConnection() ; } /** * 关闭数据库连接 */ public void close(Connection conn){ try { conn.close() ; } catch (SQLException e) { e.printStackTrace(); } } }
测试方法:
package org.lkl.singleton; import java.sql.SQLException; public class Test { public static void main(String[] args) { try { DBManager.getInstance().getConnection() ; DBManager.getInstance().getConnection() ; DBManager.getInstance().getConnection() ; DBManager.getInstance().getConnection() ; } catch (SQLException e) { e.printStackTrace(); } } }
获得的结果:
获取数据源 获取数据库连接 获取数据库连接 获取数据库连接 获取数据库连接
很明显达到了数据源的单例.
通过上面的代码可以发现数据源的配置要设置很多参数 那么一般情况下都会通过配置文件来制定数据源的配置参数的 而不是想上面那么多的setXxx方法来设置
以dbcp数据源为例:
添加jar包:
commons-dbcp-1.4.jar
commons-pool-1.6.jar
为其创建属性文件: jdbc.properties :
username = soctt password = tiger driverClassName = oracle.jdbc.driver.OracleDriver url = jdbc:oracle:thin:@localhost:1521:orcl initialSize = 4 maxActive = 8 maxIdle = 7 minIdle = 3 maxWait = 5000
那么在DataSource中增加一个方法:getDbcpDataSource :
package org.lkl.singleton; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; import org.apache.commons.dbcp.BasicDataSourceFactory; import com.mchange.v2.c3p0.ComboPooledDataSource; public class DataSource { public static ComboPooledDataSource getDataSource() throws Exception{ ComboPooledDataSource cpds = new ComboPooledDataSource() ; cpds.setDriverClass("oracle.jdbc.driver.OracleDriver"); cpds.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:orcl"); cpds.setUser("scott"); cpds.setPassword("tiger"); cpds.setMinPoolSize(5); cpds.setAcquireIncrement(5); cpds.setMaxPoolSize(20); cpds.setInitialPoolSize(3) ; System.out.println("获取数据源"); return cpds ; }
/**
* 通过配置文件获取数据源配置信息
*/ public static javax.sql.DataSource getDbcpDataSource() throws Exception{ Properties properties = new Properties(); InputStream inStream = DataSource.class.getClassLoader() .getResourceAsStream("jdbc.properties"); properties.load(inStream); javax.sql.DataSource dataSource = BasicDataSourceFactory.createDataSource(properties); Connection conn = dataSource.getConnection(); System.out.println(conn); return dataSource ; } }
拓展 : 单例这种技术并没有严格上来说只能创建一个对象 这种技术同样适用于创建固定数量的对象 例如对象池 通过对象池来控制我们的数据库连接池 那么下面来模拟实现一个对象池
注意代码中的注释
package org.lkl.singleton; import java.util.ArrayList; import java.util.List; /** *对象池 */ public class PoolManager { /** *对象池中存放的对象 */ private static class PoolItem{ Object item ; boolean flag = false ; //标记位 如对象从对象池中取出 则为true 反之 public PoolItem(Object item){ this.item = item ; } } /** * 以一个集合来保存对象池中的对象 */ private List<PoolItem> items = new ArrayList<PoolItem>() ; /** * 往对象池中增加一个对象 */ public void add(Object obj){ items.add(new PoolItem(obj)) ; } /** * 从对象池中获取一个对象 */ public Object get(){ for(int i=0 ;i<items.size() ;i++){ PoolItem item = items.get(i) ; if(!item.flag){ //在对象池中 item.flag = true ; return item.item ; //返回 } } throw new RuntimeException("对象池为空") ; } /** * 释放对象 */ public void release(Object obj){ for(int i=0 ;i<items.size() ;i++){ PoolItem item = items.get(i); if(item==obj){ item.flag = false ; break ; } } } }
通过以上的PoolManager 可以来实现一个ConnectionPool 其代码如下:
package org.lkl.singleton; import java.sql.Connection; /** * 数据库连接池 */ public class ConnectionPool { private static PoolManager pool = new PoolManager() ; /* * 可以通过程序来控制 只能从数据源中获取多少个连接 */ public static void addConnectdions(int number){ //number 表示连接池中存放多少个Connection对象 for(int i=0 ;i<number ;i++){ try { pool.add(org.lkl.singleton.DBManager.getInstance().getConnection()) ; } catch (Exception e) { e.printStackTrace(); } } } /** * 获取数据库连接 */ public static Connection getConnection(){ return (Connection)pool.get() ; } /** * 释放连接 * @param c */ public static void releaseConnection(Connection c ){ pool.release(c) ; } }
测试代码:
package org.lkl.singleton; public class Test { static{ //这里很重要 ConnectionPool.addConnectdions(5) ; //初始化只能获取5个数据库连接 } public static void main(String[] args) { try { ConnectionPool.getConnection() ; ConnectionPool.getConnection() ; ConnectionPool.getConnection() ; ConnectionPool.getConnection() ; ConnectionPool.getConnection() ; ConnectionPool.getConnection() ; } catch (Exception e) { e.printStackTrace(); } } }
由于上面试图获取6个连接 从而导致了异常 :结果如下:
获取数据源
获取数据库连接
获取数据库连接
获取数据库连接
获取数据库连接
获取数据库连接
java.lang.RuntimeException: 对象池为空
at org.lkl.singleton.PoolManager.get(PoolManager.java:44)
at org.lkl.singleton.ConnectionPool.getConnection(ConnectionPool.java:25)
at org.lkl.singleton.Test.main(Test.java:15)
以上完成单例模式的笔记.