JAVA设计模式之单例模式(Singleton)

单例模式(Singleton)

在系统设计过程中,经常有一些数据或者功能上要求在JVM的生命周期过程中,只存在一份,那么这个时候需要将某个类设计成单例(singleton)的。
比如,系统的数据字典通常为通过配置的方式存储在数据库中,系统运行过程中,如果需要读取数据字典,可以从数据库读取,但从数据库读取存在IO开销大的问题,并且数据字典运用比较广泛,所以读取的频率相对很高,数据库读取会直接降低系统的性能。
这个时候,会考虑系统启动时,直接一次性读取所有的数据字典,将数据加载到对象中,在内存中存储,这时候,我们再需要数据字典的数据,就可以直接从内存中获取了,而不需要每次都采用效率比较低的数据库读取。
此时,又会出现另外一个问题,我们希望系统中存储数据字典数据的对象在JVM中存在且只存在一份,否则仍然会出现多次读取数据库和内存浪费的情况。单例模式应运而生。

单例模式的原理

一个类之所以称之为单例的,需要满足以下的条件:

  • 1、构造器私有化,让类外的任何地方都不可以进行对象创建
  • 2、在本类内进行实例化,对外提供此对象的统一访问方式

单例模式的几种类型

  • 1、饿汉式单例模式

  • 需求:
    我们有如下的db.properties配置文件,系统运行过程中,我们不希望每次需要配置文件的数据的时候,都读取此文件,而是通过单例模式的方式进行预先加载到对象中进行存储,需要时,直接从对象中获取相应属性。

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql:///test?&useUnicode=false&characterEncoding=utf8&useSSL=false
    jdbc.user=root
    jdbc.password=password
    
  • 解决方案:
    建立存储配置文件的类,类中定义相应的属性,并私有化构造器,在构造器中直接实例化此类对象实现数据的加载,或者在类对象定义时直接初始化。

    • code
      	@Data
      	public class PropertyCache {
      	    private String driver ;
      	    private String url;
      	    private String user;
      	    private String password;
      	
      	    //实例化的对象
      	    private static PropertyCache instance = new PropertyCache();
      	
      	    //构造器中进行数据的加载
      	    private PropertyCache(){
      	        InputStream is = PropertyCache.class.getClassLoader().getResourceAsStream("db.properties");
      	        Properties p = new Properties();
      	        try {
      	            p.load(is);
      	            this.setDriver(p.getProperty("jdbc.driver"));
      	            this.setUrl(p.getProperty("jdbc.url"));
      	            this.setUser(p.getProperty("jdbc.user"));
      	            this.setPassword(p.getProperty("jdbc.password"));
      	        } catch (IOException e) {
      	            e.printStackTrace();
      	        }
      	    }
      	
      	    //对外提供统一的访问接口
      	    public static PropertyCache getInstance(){
      	        return instance;
      	    }
      	}
      
      • 测试代码
      @Test
      public void test_singleton(){
          PropertyCache instance = PropertyCache.getInstance();
          PropertyCache instance1 = instance.getInstance();
          System.out.println(instance == instance1);
      
          System.out.println(instance.getDriver());
          System.out.println(instance.getUrl());
          System.out.println(instance.getUser());
          System.out.println(instance.getPassword());
      }
      
      • 测试结果
      true
      com.mysql.jdbc.Driver
      jdbc:mysql:///test?&useUnicode=false&characterEncoding=utf8&useSSL=false
      root
      password
      
  • 2、懒汉式单例模式

    • 需求
      饿汉式中的instance,不想在lei加载的时候,就进行初始化,想在第一次真正需要的时候,再进行加载和数据的赋值,加快系统启动时的效率(lazy_loading)
    • 解决方案
      在getInstance方法中进行instance的赋值,并进行数据的装载
    • code
    @Data
    public class LazyPropertyCache {
        private String driver ;
        private String url;
        private String user;
        private String password;
    
        //实例化的对象
        private static LazyPropertyCache instance ;
    
        //构造器中进行数据的加载
        private LazyPropertyCache(){
    
        }
    
        //对外提供统一的访问接口
        public static LazyPropertyCache getInstance(){
            if(null == instance){
                synchronized (LazyPropertyCache.class) {
                    if(null == instance) {
                        instance = new LazyPropertyCache();
                        init();
                    }
                }
            }
    
            return instance;
        }
    
        //db.properties配置文件中数据的抓取和赋值
        private static void init(){
            InputStream is = LazyPropertyCache.class.getClassLoader().getResourceAsStream("db.properties");
            Properties p = new Properties();
            try {
                p.load(is);
                getInstance().setDriver(p.getProperty("jdbc.driver"));
                getInstance().setUrl(p.getProperty("jdbc.url"));
                getInstance().setUser(p.getProperty("jdbc.user"));
                getInstance().setPassword(p.getProperty("jdbc.password"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 测试代码
    @Test
    public void test_singleton2(){
        LazyPropertyCache instance1 = LazyPropertyCache.getInstance();
        LazyPropertyCache instance2 = LazyPropertyCache.getInstance();
        System.out.println(instance1 == instance2);
    
        System.out.println(instance1.getDriver());
        System.out.println(instance1.getUrl());
        System.out.println(instance1.getUser());
        System.out.println(instance1.getPassword());
    }
    
    • 测试结果
    true
    com.mysql.jdbc.Driver
    jdbc:mysql:///test?&useUnicode=false&characterEncoding=utf8&useSSL=false
    root
    password
    
    • 注意
      懒汉式就是在定义属性或者构造器内部进行instance的赋值,而留到getInstance方法内进行初始化,值得注意的式,同时调用getInstance的线程可能不只一个,如不做同步处理,则多个线程可能同时通过构造器构造出多个实例,就不能称为单例了 ,因此需要synchonize同步化处理。
      另外,在同步块中,为啥需要进行2次if判断instance是否为null,大家可以深入考虑一下。
  • 3、枚举方式实现单例模式

    • 疑问
      以上饿汉式和懒汉式的单例模式定义好后,是否真的可以确保系统中永远最多只有一个对象,有没有什么办法破解这个单例,也就是生成多个实例对象?答案是有的,我们通过下面的测试来看一下:
    • 测试
    @Test
    public void test_singleton3() throws Exception {
        PropertyCache instance = PropertyCache.getInstance();
        //对象序列化入文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\instance")));
        oos.writeObject(instance);
        //从文件读入进行反序列化
        ObjectInputStream ois = new ObjectInputStream((new FileInputStream(new File("D:\\instance"))));
        PropertyCache instance2 = (PropertyCache) ois.readObject();
    
        System.out.println(instance == instance2);
        System.out.println(instance2.getDriver());
        System.out.println(instance2.getUrl());
        System.out.println(instance2.getUser());
        System.out.println(instance2.getPassword());
    }
    
    • 结果
    false
    com.mysql.jdbc.Driver
    jdbc:mysql:///test?&useUnicode=false&characterEncoding=utf8&useSSL=false
    root
    password
    
    • 解决方式
      我们知道枚举这个结构,就是从jvm底层保证了唯一性,我们使用Enum来进行一下实验
    • code
    	public enum EnumPropertyCache implements Serializable {
        INSTANCE;
    
        private String driver ;
        private String url;
        private String user;
        private String password;
    
        public String getDriver() {
            return driver;
        }
    
        public String getUrl() {
            return url;
        }
    
        public String getUser() {
            return user;
        }
    
        public String getPassword() {
            return password;
        }
    
        private void setDriver(String driver) {
            this.driver = driver;
        }
    
        private void setUrl(String url) {
            this.url = url;
        }
    
        private void setUser(String user) {
            this.user = user;
        }
    
        private void setPassword(String password) {
            this.password = password;
        }
    
        private EnumPropertyCache(){
            InputStream is = PropertyCache.class.getClassLoader().getResourceAsStream("db.properties");
            Properties p = new Properties();
            try {
                p.load(is);
                this.setDriver(p.getProperty("jdbc.driver"));
                this.setUrl(p.getProperty("jdbc.url"));
                this.setUser(p.getProperty("jdbc.user"));
                this.setPassword(p.getProperty("jdbc.password"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 测试代码和结果
    @Test
    public void test_singleton4(){
        EnumPropertyCache instance = EnumPropertyCache.INSTANCE;
        EnumPropertyCache instance2 = EnumPropertyCache.INSTANCE;
    
        System.out.println(instance == instance2);
        System.out.println(instance.getDriver());
        System.out.println(instance.getUrl());
        System.out.println(instance.getUser());
        System.out.println(instance.getPassword());
    }
    
    true
    com.mysql.jdbc.Driver
    jdbc:mysql:///test?&useUnicode=false&characterEncoding=utf8&useSSL=false
    root
    password
    
    • 序列化方式测试
    @Test
    public void test_singleton3() throws Exception {
        EnumPropertyCache instance = EnumPropertyCache.INSTANCE;
        //对象序列化入文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\instance")));
        oos.writeObject(instance);
        //从文件读入进行反序列化
        ObjectInputStream ois = new ObjectInputStream((new FileInputStream(new File("D:\\instance"))));
        EnumPropertyCache instance2 = (EnumPropertyCache) ois.readObject();
    
        System.out.println(instance == instance2);
        System.out.println(instance2.getDriver());
        System.out.println(instance2.getUrl());
        System.out.println(instance2.getUser());
        System.out.println(instance2.getPassword());
    }
    
    • 测试结果
    true
    com.mysql.jdbc.Driver
    jdbc:mysql:///test?&useUnicode=false&characterEncoding=utf8&useSSL=false
    root
    password
    

从测试结果中可以看出,尽管经过了序列化和反序列化的过程,最终的instance都是同一个instance,也就是说jvm从底层保证了Enum枚举类型的唯一。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值