引入
何为单间模式?简单的描述,用来创建独一无二的,只能有一个实例的对象的入场券。
定义:确保一个类只有一个实例,并提供一个全局访问点
揭破经典的单例模式实现
package com.zpkj.project10;
public class Singleton {
//利用一个静态变量来记录Singleton的唯一实例
private static Singleton uniqueInstance;
//构造器私有,只有内部才能调用构造器
private Singleton() {
super();
}
//返回实例对象
public static Singleton getInstance(){
/**
* 为空,还没有创建实例,如果我们不需要这个实例,它就永远不会被创建,这就是"延迟实例化"
* 懒汉模式
*/
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
//其他方法
}
UML类图
在多线程环境下的改造
我们写两个线程来验证多线程下是否还能获取唯一的实例对象
package com.zpkj.project10;
public class Thread1 extends Thread{
@Override
public void run() {
super.run();
Singleton instance = Singleton.getInstance();
System.out.println(instance);
}
}
package com.zpkj.project10;
public class Thread2 extends Thread{
@Override
public void run() {
super.run();
Singleton instance = Singleton.getInstance();
System.out.println(instance);
}
}
package com.zpkj.project10;
public class Main {
public static void main(String[] args) {
Thread thread = new Thread1();
Thread thread2 = new Thread2();
thread.start();
thread2.start();
}
}
结果如下:
结果并非我们预期的那样,所以继续改进我们的代码
处理多线程
public class Singleton {
//利用一个静态变量来记录Singleton的唯一实例
private static Singleton uniqueInstance;
//构造器私有,只有内部才能调用构造器
private Singleton() {
super();
}
//返回实例对象
public static synchronized Singleton getInstance(){
/**
* 为空,还没有创建实例,如果我们不需要这个实例,它就永远不会被创建,这就是"延迟实例化"
* 懒汉模式
*/
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
//其他方法
}
通过增加synchronized关键字,迫使每个线程在进入这个方法之前,要先等待别的线程离开该方法,这样就不会有多个线程同时进入这个方法。
虽然解决了问题,但同时降低了性能,我们目的其实是只有在第一次执行此方法时,才需要同步,一旦设置了uniqueInstance变量,就不需要同步这个方法了,那么之后的每次调用同步都是一种累赘。
改善多线程
package com.zpkj.project10;
public class Singleton2 {
/**
* jvm在加载这个类时马上创建唯一的单间实例,保证了任何线程在访问uniqueInstance静态变量之前,一定先创建此实例
* 利用"急切"创建实例,而不使用"延迟实例化的做法"
* 也就是一般所说的"饱汉模式"
*/
private static Singleton2 uniqueInstance = new Singleton2();
private Singleton2() {
super();
}
public static Singleton2 getInstance(){
return uniqueInstance;
}
//其他方法
}
package com.zpkj.project10;
public class Singleton3 {
/**
* volatile关键字确保,当uniqueInstance变量被初始化成Singleton3实例时,
* 每个线程从主存中读取uniqueInstance的值,保证正确处理uniqueInstance变量
*/
private volatile static Singleton3 uniqueInstance ;
private Singleton3() {
}
/**
* 双重检查加锁
*/
public static Singleton3 getInstance(){
if(uniqueInstance==null){
synchronized (Singleton3.class) {
if(uniqueInstance==null){
uniqueInstance = new Singleton3();
}
}
}
return uniqueInstance;
}
}
使用场景
使用单间模式的目的是在任何时刻都只有一个对象,常常用来被管理共享的资源,例如数据库连接池或线程池
示例
下面一个例子是本人,在前段时间项目中写过的代码,根据不同的数据源信息创建连接池
package com.vk.app.model.utils.dbcp;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sgcc.uap.persistence.IHibernateDao;
import com.vk.app.util.ApplicationBeanUtils;
import com.vk.databus.StringUtil;
/**
* @ClassName: DBCPUtil
* @Description: TODO(创建连接池)
* @author liyang
* @date 2018-8-14 下午2:46:24
*
*/
public class DBCPUtil {
private static Log log = LogFactory.getLog(DBCPUtil.class);
private static Properties properties =new Properties();
private static Map<Long, DataSource> dataSource = new HashMap<Long, DataSource>();
private DBCPUtil() {
}
static {
try {
InputStream is = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
log.error("初始化dbcp.properties错误");
}
}
/**
* 数据源id
*/
public static Connection createOrGetDbcpPoll(Long vkId) throws SQLException{
//存在直接从池中取
if(!StringUtil.isEmpty(dataSource.get(vkId)))
{
return dataSource.get(vkId).getConnection();
}
//不存在,创建连接池
try{
dataSource.put(vkId, BasicDataSourceFactory.createDataSource(new DBCPUtil().setProperties(vkId, properties)));
}catch (Exception e) {
e.printStackTrace();
log.error("创建连接池错误");
}
return dataSource.get(vkId).getConnection();
}
@SuppressWarnings("unchecked")
private Properties setProperties(Long vkId, Properties properties){
//获取数据库链接信息
IHibernateDao dao = ApplicationBeanUtils.getBean(IHibernateDao.class);
List<Map<String, Object>> jdbcRes = dao.queryForListWithSql("select * from VK_DS_JDBC where VK__ID = ?",new Object[]{vkId});
if(jdbcRes.size() == 0){
log.error("未找到jdbc数据源信息");
return null;
}
Map<String, Object> jdbc = jdbcRes.get(0);
properties.setProperty("driverClassName", jdbc.get("DS_DRIVER").toString());
properties.setProperty("url", jdbc.get("JDBC_URL").toString());
properties.setProperty("username", jdbc.get("USER_NAME").toString());
properties.setProperty("password", jdbc.get("USER_PASSWORD").toString());
return properties;
}
}
总结
1.单例模式确保程序中一个类最多只有一个实例
2.单例模式也提供访问这个实例的全局点。
3.在java中实现单例模式需要私有的构造器、一个静态方法,和一个静态变量。
4确定在性能和资源上的限制,然后小心地选择适当的方案来实现单例,以解决多线程的问题(我们必须认定所有的程序都是多线程的)
5.小心,你如果使用多个类加载器,可能导致单例失效而产生多个实例。
引用
[1] 弗里曼. Head First 设计模式(中文版)[Z]. 中国电力出版社: O'Reilly Taiwan公司 ,2007.