设计模式04-单例模式

单例模式保证一个类仅有一个实例 并且提供一个全局的访问点

在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)

 

 以上完成单例模式的笔记. 

原文地址:https://www.cnblogs.com/liaokailin/p/3627339.html