设计模式学习(一) 单例模式

版权声明:有抱负的小狮子 https://blog.csdn.net/weixin_38087538/article/details/82688241

引入


何为单间模式?简单的描述,用来创建独一无二的,只能有一个实例的对象的入场券。

定义:确保一个类只有一个实例,并提供一个全局访问点

揭破经典的单例模式实现

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.

源码下载

https://github.com/isheroleon/design

猜你喜欢

转载自blog.csdn.net/weixin_38087538/article/details/82688241