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

一、概述

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

二、解决问题

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

三、结构类图

  

四、成员角色

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

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

五、应用实例

   单例模式大概有三种实现方案,注意都不是最优方案,我们先来看看如何实现。  

  1.“急切”创建实例

package singleton.pattern;

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

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

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;
	}
}

3.内部类创建单例。利用类只会加载一次的机制实现

public class InnerClassSingleton {
    private static volatile boolean isFirstCreate = true;
    private InnerClassSingleton() {
    }
    public static InnerClassSingleton getInstance() {
        return MyInnerClass.instance;
    }
 
    private static class MyInnerClass {
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
}

 

上面几种不是最完美的方案,因为我们创建对象有多种方式,除了new出来,还有反射,序列化,和克隆,为了把其他创建实例的路子堵住,我们选上面第三种方案来做优化。

 

public class InnerClassSingleton implements Cloneable, Serializable{
    private static volatile boolean isFirstCreate = true;
    private InnerClassSingleton() {
        // 防止反射创建对象破坏单例
        if (isFirstCreate) {
            synchronized (InnerClassSingleton.class) {
                if (isFirstCreate) {
                    isFirstCreate = false;
                } else {
                    throw new RuntimeException("单例已经创建");
                }
            }
        } else {
            throw new RuntimeException("单例已经创建");
        }
    }
    public static InnerClassSingleton getInstance() {
        return MyInnerClass.instance;
    }
 
    private static class MyInnerClass {
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
 
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 防止克隆破坏单例,如果调用就抛出异常
        throw new UnsupportedOperationException();
    }
 
    // 防止序列化对象破坏单例
    private Object readResolve() {
        return MyInnerClass.instance;
    }
}

 

测试代码如下:

public static void main(String[] args) throws CloneNotSupportedException, IOException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 急切实例化
        System.out.println(HurrySingleton.getInstance());
        System.out.println(HurrySingleton.getInstance());
        // 内部类
        System.out.println(InnerClassSingleton.getInstance());
        System.out.println(InnerClassSingleton.getInstance());
        // 同步方法+双重检测
        System.out.println(DoubleCheckSingleton.getInstance());
        System.out.println(DoubleCheckSingleton.getInstance());
 
        // 破坏单例
        // 第一,克隆。其实这个不容易破坏的,因为需要在单例类里实现Cloneable接口,并且重写clone方法。
        // 如果是单例了,谁还会去这样做?
        System.out.println("单例对象:" + InnerClassSingleton.getInstance());
        System.out.println("克隆的对象:" + InnerClassSingleton.getInstance().clone());
 
        // 第二,序列化。这个也不容易破坏,因为需要实现Serializable接口才能序列化对象,单例就不要去实现就好了
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(InnerClassSingleton.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        InnerClassSingleton deserialable = (InnerClassSingleton) ois.readObject();
        System.out.println("单例对象:" + InnerClassSingleton.getInstance());
        System.out.println("序列化的对象:" + deserialable);
        bos.close();
        oos.close();
        bis.close();
        ois.close();
 
        // 第三,反射。反射比较容易破坏单例,这个要做好防范
        Constructor<InnerClassSingleton> constructor = InnerClassSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerClassSingleton reflectInstance = constructor.newInstance();
        System.out.println("单例对象:" + InnerClassSingleton.getInstance());
        System.out.println("反射的实例:" + reflectInstance);
    }

单例模式在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() + "\n");
	            
	            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.如果使用多个类加载器,要小心单例失效而产生多个实例。

posted @ 2016-08-01 22:21  jenkinschan  阅读(780)  评论(0编辑  收藏  举报