• 设计模式(2)---单例模式


    上一篇:设计模式基本概述

    1.什么是单例模式?

      单例模式是java种最简单的设计模式之一,它提供了一种创建对象的最佳方式。此种设计模式保证一个类只有一个实例,并且提供一个访问该实例的全局访问点(就像是一个学校只有一个校长)。

    在这里插入图片描述

    2.单例模式的优点?

      1.单例模式因为在内存中只有一个实例,避免了频繁地创建实例、销毁实例,所以极大地节省了系统资源地开销。

      2.设置全局访问点,优化共享资源访问。

    3.常见地单例模式?

    1.饿汉式

      此种设计模式线程安全,调用效率高,不能够延时加载

    /**
     * 饿汉单例模式
     */
    public class Singleton01 {
    
    	//让构造函数私有化,这样就不会被实例化
    	private Singleton01() {
    	}
    
    	//创建Singleton对象(因为使用了static关键字,所以类加载地时候就会被初始化,所以不会有线程安全问题)
    	private static Singleton01 instance = new Singleton01();
    
    	//获取唯一可用对象,因为线程安全,所以不需要使用synchronized关键字,效率更高
    	public static Singleton01 getInstance() {
    		return instance;
    	}
    }
    

      饿汉式不管这个对象用不用,反正类已加载地时候就会创建,如果想要在使用地时候再创建此对象,则可以使用懒汉式。

    2.懒汉式

      此种设计模式线程不安全(可用synchronized解决),调用效率不高,能够延时加载

    /**
     * 懒汉式
     *
     * 懒嘛!要用地时候才去创建对象
     */
    public class Singleton02 {
    
    	//构造方法私有化
    	private Singleton02() {
    	}
    
    	//类初始化地时候,不立即加载对象
    	private static Singleton02 instance;
    
    	/**
    	 *给一个公共地获取对象地方法(懒汉式本来线程不安全,但是可以使用synchronized关键字保证线程安全,
    	 * 
    	 * 也正是使用了synchronized关键字使得调用效率降低
    	 */
    	public static synchronized Singleton02 getInstance(){
    		if(instance==null){
    			instance=new Singleton02();
    		}
    		return instance;
    	}
    }
    

    代码注释中说了这种懒汉式的单例虽然可以使用synchronized关键字使得线程安全,但是效率降低了,为了提高效率,则可以使用DCL( double-checked locking)双重校验锁

    3.DCL( double-checked locking)双重校验锁
    /**
     * DCL双重校验锁
     */
    public class Singleton03 {
    
    	//1.构造方法私有化
    	private Singleton03() {
    	}
    
    	//2.类初始化的时候,不立即加载对象
    	private static Singleton03 instance;
    
    	//3.给个获取该对象的公共方法
    
    	public static Singleton03 getInstance() {
    		//判断instance是否为空
    		if (instance == null) {
    			//instance为空立即上锁
    			synchronized (Singleton03.class) {
    				//再次判断instance是否为空
    				if (instance == null) {
    					instance = new Singleton03();
    				}
    			}
    		}
    		return instance;
    	}
    
    }
    

    DCL双重校验锁不是上来就直接将整个publice方法给锁上而是将锁更加细化,不仅保证了对象在使用的时候才去创建而且提高了效率。但是!

      DCL这种方式因为JMM(java内部模型)的原因,偶尔可能会出问题:

    因为不是原子性操作,在内存中一般会经历下面3步:

    1)分配内存
    2)执行构造方法
    3)指向地址

    而在执行代码的时候,为了提高性能,编译器和处理器常常会对指令进行重排序。重排序分为三种:编译器重排序,指令级并行重排序,内存系统重排序,实现优化,优化结果可能是

    1)初始化 Singleton4 对象;

    2)把 Singleton4 对象地址赋给instance变量

    也有可能是这样

    1)初始化一半Singleton4对象;

    2)把Singleton4对象地址赋给instance变量

    3)初始化剩下的Singleton4对象;

    如果是第二种,则DCL在多线程的情况下可能出现的情况:当第一个线程判断instance==nulltrue,进入synchronized内,执行instance=new Singleton03()的时候,初始化了一般就将地址赋给instance变量,此时第二个线程判断instance==null的时候发现instance不为null,然后执行了return instance,而实际上此时第一个线程可能正在执行剩下的instance=new Singleton03()

    我们可以使用volatile关键字能够大幅避免此种情况,但是也只能避免指令重排,继续优化有一种静态内部类式

    4.静态内部类式
    /**
     * 静态内部类式
     */
    public class Singleton04 {
    
    	//构造方法私有化
    	private Singleton04() {
    	}
    
    	//创建静态内部类
    	private static class InnerClass {
    		//在静态内部类中创建对象实例化
    		private static final Singleton04 instance=new Singleton04();
    	}
    
    	public static Singleton04 getInstance(){
    		return InnerClass.instance;
    	}
    }
    

      此种方式:外部类没有静态属性,不会在类加载的时候就初始化,只有调用了getInstance()方法的时候才回去加载静态内部类,加载的时候线程安全所以不用考虑线程安全问题。所以此种模式又保证了线程安全,又符合lazy loading(延时加载)

      到了第4种方式应该说已经属于非常优秀的了,但是java中有一种非常牛X的机制叫反射,它能够无视你是否是private的,一得就能得到!这也导致了静态内部类式也可能被破坏单例;

    /**
     * 静态内部类式
     */
    public class Singleton04 {
    
    	//构造方法私有化
    	private Singleton04() {
    	}
    
    	//创建静态内部类
    	private static class InnerClass {
    
    		//在静态内部类中创建对象实例化
    		private static final Singleton04 instance = new Singleton04();
    	}
    
    	public static Singleton04 getInstance() {
    		return InnerClass.instance;
    	}
    
    }
    
    class test {
    
    	public static void main(String[] args) throws Exception {
    		Singleton04 instance = Singleton04.getInstance();
    
    		//使用反射破坏单例
    		//通过class对象获取构造器
    		Constructor<Singleton04> singleton04Constructor = Singleton04.class.getDeclaredConstructor(null);
    		//设置无视private
    		singleton04Constructor.setAccessible(true);
    		//创建一个实例
    		Singleton04 singleton04 = singleton04Constructor.newInstance();
    
    		boolean result = instance == singleton04;
    		System.out.println("两个实例对象是否一样:" + result);
    	}
    }
    

    在这里插入图片描述
    从结果来看,使用反射直接破坏了单例,那么之前的几种单例实现方式也都可以通过反射进行破坏。

    为了防止通过反射来破坏单例,我们可以使用枚举

    5.枚举

      枚举是在JDK1.5开始引入的,它线程安全、不能延时加载、调用效率高

    在这里插入图片描述
    我们通过反射创建对象实例的newInstance()方法的源码中就说了如果是枚举会抛出异常!
    在这里插入图片描述
    所以枚举是一种比较推荐的单例模式写法

    /**
     * 枚举
     */
    public enum Singleton05 {
    	INSTANCE;
    
    	public Singleton05 getInstance() {
    		return INSTANCE;
    	}
    }
    
    /**
     * 测试
     */
    class Test {
    
    	public static void main(String[] args) {
    		Singleton05 instance1 = Singleton05.INSTANCE;
    		Singleton05 instance2 = Singleton05.INSTANCE;
    
    		boolean result = instance1 == instance2;
    		System.out.println("两个实例是否一致:" + result);
    
    	}
    }
    

    下一篇:工厂模式

  • 相关阅读:
    JavaScript 获取来源地址
    JavaScript 调试&显示变量
    JavaScript Math对象
    JavaScript 封闭函数
    常见泛型委托
    使用BindingSource绑定数据库
    Case Reply
    RSS订阅
    ADO.NET
    泛型的优点
  • 原文地址:https://www.cnblogs.com/wgty/p/12810440.html
Copyright © 2020-2023  润新知