zoukankan      html  css  js  c++  java
  • 单例设计模式

       

       JVM将整个运行环境当做一个单例对象。

    要点:

    1. 某个类只能有一个实例。
    • 构造器私有化
    1. 它必须自行创建这个实例
    • 含有一个该类的静态变量来保存这个唯一实例
    1. 它必须自行向整个系统提供这个实例
    • 对外提供获取该实例对象的方式
    1. 直接暴露
    2. 用静态变量的get方法获取

    几种常见形式:

    1. 饿汉式:直接创建对象,不存在线程安全问题
      1. 直接实例化饿汉式(简洁直观)
      2. 枚举式(最简洁)
      3. 静态代码块的饿汉式(适合复杂实例化)
    2. 懒汉式:延迟创建对象
      1. 线程不安全(适用于单线程)
      2. 线程安全(使用与多线程)
      3. 静态内部类形式(适用于多线程)

    饿汉就是很着急,想吃东西,无论我当前要不要这些实例,它都着急着把它创建出来。

    懒汉就是很懒,不到万不得已,不会去做,

     

    我们先来总结一下刚刚提到的几点:

    • 构造器虚拟化
    • 自行创建,用静态变量存储
    • 向外提供这个实例(因为是用静态变量存储,所以可以通过类名调用)
    • 强调单例,可以使用final修饰

    先来写饿汉式的单例,直接创建这个对象,无论是否需要它:

        什么时候不需要这个静态对象呢?比如该类中含有静态方法,我可以通过类名直接调用,这时就没有这个对象啥事了,比如下图中的test()方法,但由于我们的INSTANCE是静态变量,在类加载时就已经创建,是不管test()用没用到的,都会创建出来。

    在JDK1.5引入枚举之后,这种方式有更简洁的写法。

    枚举类型,表示该类型对象是有限的几个,我们可以限定为一个,就成了单例。

    非常简洁,但效果和我们之前写的第一种方法完全一样,获取方式也是可以通过类名.INSTANCE的方式获取。

    第三种方式,看起来和第一种没啥太大区别,无非就是实例化使用静态代码块的方式实现,那我们什么时候回用到它?

    比如说,我们有属性需要初始化:

    这样写自然没问题,不过如果我们是通过配置文件的方式配置的,那该怎么办?

    很显然,一二种方法很难做到这一点,第三种方法就可以在加载类时,将配置文件加载进来

     

    为什么说饿汉模式不存在线程安全?因为JAVA中这些静态初始化的代码都会合成为<clinit>,<clinit>是线程安全方法,他们三个都是在类初始化的时候,直接创建的对象的。

     

    接下来说说懒汉式:

    要等到需要用这个单例时,我们才创建它,这样最初的时候instance应该是空的,为了避免外界在它还是空的时候通过类名.的方式获取,所以把instance设定为private。

    在getInstance中,如果instance是空的,那么创建,如果不是,直接返回。

    做一个测试:

                    Singleton4 singleton1 = Singleton4.getInstance();
    		Singleton4 singleton2 = Singleton4.getInstance();
    		System.out.println(singleton1==singleton2);
    		System.out.println(singleton1);
    		System.out.println(singleton2);            
    

      

    在单线程情况下,这个是没有问题的,如果是多线程呢?

    也可以做一个测试,通过线程池的方式:

                    Callable<Singleton4> c = new Callable<Singleton4>() {
    
    			@Override
    			public Singleton4 call() throws Exception {
    				
    				return Singleton4.getInstance();
    			}
    			
    		};
    		
    		ExecutorService es = Executors.newFixedThreadPool(2);
    		Future<Singleton4> f1 = es.submit(c);
    		Future<Singleton4> f2 = es.submit(c);
    		
    		Singleton4 s1 = f1.get();
    		Singleton4 s2 = f2.get();
    		
    		System.out.println(s1==s2);
    		System.out.println(s1);
    		System.out.println(s2);
    

      

    为了看出效果,我们在创建单例时加一个休眠代码:

    	public static Singleton4 getInstance() {
    		if(instance==null) {
    			
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			instance = new Singleton4();
    		}
    		return instance;
    	}
    

      

    结果很显然,在线程1发现instance没有初始化时,会进入if块中初始化instance,在线程1还没有来得及创建instance时,切换到线程2,它也发现instance没有初始化,也进入if块中,因此出现了两个instance,不符合单例的要求

    我们可以用一个很暴力的方式,直接用synchronize包住这些代码,将Singlgton.class作为锁对象

    package singleton;
    
    public class Singleton5 {
    	private static Singleton5 instance;
    
    	private Singleton5() {
    
    	}
    
    	public static Singleton5 getInstance() {
    		synchronized (Singleton5.class) {
    			if (instance == null) {
    
    				try {
    					Thread.sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				instance = new Singleton5();
    			}
    			return instance;
    		}
    	}
    
    }
    

      

        做个测试:

        当然这还不是最优版,最初来的时候,instance还没初始化,为了安全我们使用了synchronize,但是后面的获取行为就没有必要使用这个及其影响性能的synchronize了,所以用一个if可以大大提高性能

    package singleton;
    
    public class Singleton6 {
    	private static Singleton6 instance;
    
    	private Singleton6() {
    
    	}
    
    	public static Singleton6 getInstance() {
    		if(instance==null) {
    			synchronized (Singleton6.class) {
    				if (instance == null) {
    
    					try {
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					instance = new Singleton6();
    				}
    				
    			}
    		}
    		
    		return instance;
    		
    	}
    
    }
    

      

        最后一种是又简单,又有效的方式,在静态内部类加载初始化时,才创建INSTANCE实例对象,静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载初始化的

    package singleton;
    
    public class Singleton7 {
    	;
    	
    	private Singleton7() {
    		
    	}
    	
    	private static class Inner{
    		private  static final Singleton7 INSTANCE = new Singleton7();
    	}
    	
    	public static Singleton7 getInstance() {
    		return Inner.INSTANCE;
    	}
    }
    

      

  • 相关阅读:
    Elasticsearch Mantanence Lessons Learned Today
    RabbitMQ Exchange & Queue Design Trade-off
    Understanding RabbitMQ Exchange & Queue
    Behind RabbitMQ Exchange Types
    七步,搭建基于Windows平台完美Jekyll博客环境
    How to Change RabbitMQ Queue Parameters in Production?
    Android Weekly Notes Issue #237
    Android Weekly Notes Issue #236
    Android Weekly Notes Issue #235
    Android Weekly Notes Issue #234
  • 原文地址:https://www.cnblogs.com/figsprite/p/10799576.html
Copyright © 2011-2022 走看看