• 设计模式(六):单例模式


    一、概述

      单例模式确保一个类只有一个实例,并提供一个安全的访问点。

    二、解决问题

      从概述中我们知道,单例模式就是保证系统的一个类只有一个实例。它的作用就是控制受限资源的访问,确保任何时刻都只有一个线程在访问一个受保护的资源。或者确保行为和状态的一致性,避免异常行为。在java web的程序中可能用到最多单例的地方就是jdbc的线程池。

    三、结构类图

      

    四、成员角色

      实例变量(uniqueInstance):持有唯一的单例实例,静态私有访问权限,只有本类才能访问该实例变量。

      全局访问方法(getInstance):提供全局的对单例实例的访问,任何时候都返回同一个单例实例(uniqueIntance),公共静态访问权限,任何对象都可以访问到。

    五、应用实例

       单例模式有三种实现方案,我们来一起来看看如何实现。

      1.同步getInstance()方法

    package singleton.pattern;
    
    public class SynchronizedSingleton {
    	//唯一的实例,只有本类才可以访问
    	private static SynchronizedSingleton uniqueInstance;
    	//只有本类才可以实例自己
    	private SynchronizedSingleton(){
    		
    	}
    	//提供对实例的全局访问点(同步方法,任何时候都只有一个线程可以访问该方法)
    	public synchronized static SynchronizedSingleton getInstance(){
    		if(uniqueInstance == null){
    			uniqueInstance = new SynchronizedSingleton();
    		}
    		return uniqueInstance;
    	}
    }   

      2.“急切”创建实例

    package singleton.pattern;
    
    public class HurrySingleton {
    	//唯一的一个类实例,用static把它变为静态的,在静态初始化器中创建单例
    	private static HurrySingleton uniqueInstance = new HurrySingleton();
    	
    	private HurrySingleton(){		
    	}
    	
    	//已经有实例了直接返回
    	public static HurrySingleton getInstance(){
    		return uniqueInstance;
    	}
    }

      3.用“双重检查加锁”,减少同步

    package singleton.pattern;
    
    public class DoubleCheckSingleton {
    	private volatile static DoubleCheckSingleton uniqueInstance;
    	private DoubleCheckSingleton(){
    		
    	}
    	
    	public static DoubleCheckSingleton getInstance(){
    		//线程只有第一次进入该方法时才会执行这个if代码
    		if(uniqueInstance == null){
    			//第一个线程获得类对象锁,其他线程就不能通过getInstance()方法获得实例了,
    			synchronized(DoubleCheckSingleton.class){
    				//这if判断是为了给第二个或者之后的线程获得类对象锁后使用,如果没有这个if判断,将会创建多个实例。
    				//另外uniqueInstance变量必须要用volatile修饰,确保第一个线程实例化了对象后,其他线程能够立刻可见
    				//volatile的详解请参考:http://www.cnblogs.com/dolphin0520/p/3920373.html(Java并发编程:volatile关键字解析)
    				if(uniqueInstance == null){
    					uniqueInstance = new DoubleCheckSingleton();
    				}
    			}
    		}
    		
    		return uniqueInstance;
    	}
    }

      单例模式在jdbc中的应用,下面采用双重检查加锁实现数据库连接池的单例

    import org.apache.commons.dbcp.BasicDataSource;
    import org.apache.commons.dbcp.BasicDataSourceFactory;
    
    import java.sql.SQLException;
    import java.sql.Connection;
    import java.sql.Statement;
    import java.util.Properties;
    
    public class ConnectionMgr {
    	
    	    private volatile static BasicDataSource dataSource = null;
    
    	    private ConnectionMgr() {
    	    }
    
    	    private static void init() {
    
    	        if (dataSource != null) {
    	            try {
    	                dataSource.close();
    	            } catch (Exception e) {
    	                //
    	            }
    	            dataSource = null;
    	        }
    
    	        try {
    	            Properties p = new Properties();
    	            p.setProperty("driverClassName", MySys.getDriver());
    	            p.setProperty("url", MySys.getDatabaseUrl());
    	            p.setProperty("password", MySys.getDatabasePassword());
    	            p.setProperty("username", MySys.getDatabaseUser());
    	            System.out.print("OMS DB URL = "+MySys.getDatabaseUrl() + "
    ");
    	            
    	            p.setProperty("maxActive", "30"); //最大连接数量
    	            p.setProperty("maxIdle", "10");   //最大空闲连接
    	            p.setProperty("maxWait", "1000"); //超时等待时间以毫秒为单位 1000等于60秒
    	            
    	            p.setProperty("removeAbandoned", "false"); //是否自动回收超时连接
    	            p.setProperty("removeAbandonedTimeout", "120"); //超时时间(以秒数为单位)
    	            
    	            p.setProperty("testOnBorrow", "true"); //指明是否在从池中取出连接前进行检验
    	            p.setProperty("testOnReturn", "false"); //指明是否在归还到池中前进行检验
    	            p.setProperty("testWhileIdle", "false");//指明连接是否被空闲连接回收器(如果有)进行检验
    	            
    	            p.setProperty("logAbandoned", "true"); //连接被泄露时是否打印
    	            p.setProperty("validationQuery", MySys.getTestQuery()); //
    
    	            dataSource = (BasicDataSource) BasicDataSourceFactory.createDataSource(p);
    
    	        } catch (Exception e) {
    	            //
    	        }
    	    }
    
    
    	    public static Connection getConnection() throws  SQLException {
    	        Connection conn = null;
    	        //双重检查加锁
    	        if (dataSource == null) {
    	        	synchronized(BasicDataSource.class){
    	        		if (dataSource == null) {
    	        			init();
    	        		}
    	        	}
    	            
    	        }
    	        
    	        if(dataSource != null){
    	            conn = dataSource.getConnection();
    	        }
    	        return conn;
    	    } 

    六、优缺点

      1、同步getInstance()方法:最安全的单例,但是每次访问单例实例都要加锁,增加了性能的开销。如果系统对性能要求不高可以用。

      2、急切实例化:解决了“延迟实例化”带来的访问延迟问题,但会影响系统的启动负担,而如果该实例一开始创建了却一直没被用到会造成资源的浪费。系统启动和运行负担小可以用。

      3、双重检查加锁:减少了同步的使用,降低了jdk同步的开销;但是低版本的JDK不能使用,必须是1.5版本或者以上版本的JDK才能使用。系统对性能要求高时使用。

    七、应用场景

       系统需要保证唯一的实例时使用,例如数据库连接池和线程池。

    八、总结

      1.单例模式确保一个类中最多只有一个实例,并且提供访问这个实例的全局访问点。

      2.实现单例模式需要私有的构造器,一个私有静态变量和一个能够被公共访问的静态方法。

      3.如果使用多个类加载器,要小心单例失效而产生多个实例。

  • 相关阅读:
    BZOJ2756:[SCOI2012]奇怪的游戏(最大流,二分)
    AtCoder Grand Contest
    BZOJ2565:最长双回文串(Manacher)
    BZOJ2160:拉拉队排练(Manacher)
    BZOJ3790:神奇项链(Manacher)
    BZOJ2342:[SHOI2011]双倍回文(Manacher)
    BZOJ4887:[TJOI2017]可乐(矩阵乘法)
    BZOJ2555:SubString(SAM,LCT)
    BZOJ1396:识别子串(SAM)
    luogu P1080 国王游戏
  • 原文地址:https://www.cnblogs.com/jenkinschan/p/5724534.html
Copyright © 2020-2023  润新知