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

      HeadFirst中对单例模式的定义:单例模式确保一个类只有一个实例,并只提供一个全局访问点。

      单例模式的应用:任务管理器、回收站、项目的配置文件、日志文件等等

      单例模式的特点:单例模式只有一个实例,减少了系统的开销,当一个对象的产生需要很多资源时,就可以通过在启动时来创建一个实例永久的驻存。

      可以在全局设置访问点,优化资源的访问。

      单例模式的常见实现方式:

      饿汉式:线程安全,效率高,不能延时加载。
      懒汉式:线程安全,效率低,可以延时加载。
      双重检测锁:线程安全,效率高,可以延时加载,但是只有在java1.5之后才支持,并且由于JVM底层模型的原因容易出问题。
      静态内部类:线程安全,效率高,可以延时加载。
      枚举:线程安全,效率高,不能延时加载,可以天然的防止反射和反序列化漏洞。

      一.饿汉式

        1.静态初始化是天然的线程安全的。
        2.效率比较高
        3.一开始就创建,没有延时加载,如果一直没有用到这个单例对象的话就浪费了资源。

       

    package com.wxisme.singleton;
    /**
     * 饿汉式单例模式
     * @author wxisme
     *
     */
    
    public class SingletonOne {
    	//静态初始化时就new出单例对象
    	private static final SingletonOne instance = new SingletonOne();
    	//私有构造器
    	private SingletonOne() {
    		
    	}
    	//返回单例对象
    	public static SingletonOne getInstance() {
    		return instance;
    	}
    }
    

     二.懒汉式

        
       1.线程安全,但是效率较低。
       2.延时加载,不会造成资源的浪费。

     

    package com.wxisme.singleton;
    
    import java.io.Serializable;
    
    /**
     * 懒汉式实现单例模式
     * @author wxisme
     *
     */
    
    public class SingletonTow implements Serializable {
    	//在获取的时候创建此处不能加final
    	private static SingletonTow instance;
    	
    	private SingletonTow() {
    		//防止反射破解
    		/*
    		if(instance != null) {
    			throw new RuntimeException();
    		}
    		*/
    		
    	}
    	//必须要手动加锁,达到线程安全的目的。
    	public synchronized static SingletonTow getInstance() {
    		if(instance == null) {
    			instance = new SingletonTow();
    		}
    		return instance;
    	}
    	//防止反序列化破解
    	/*
    	private Object readResolve() {
    		return instance;
    	}
    	*/
    	//防止反射破解
    	/*
    	private static Class getClass(String classname)
                throws ClassNotFoundException {
    		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();   
    
    		if(classLoader == null)   
    			classLoader = SingletonTow.class.getClassLoader();   
    
    		return (classLoader.loadClass(classname));   
    		
    }   
    */
    
    }
    

    三.双重检验锁

        1.对懒汉式进行改进,只需要在第一次调用getInstance()方法的时候枷锁,提高了效率。

        2.由于JVM底层模型问题,这种方式偶尔会出问题。在JDK1.5之后才能支持。

        volatile  用来确保线程安全。

     

    package com.wxisme.singleton;
    /**
     * 双重检验锁实现单例模式
     * @author wxisme
     *
     */
    
    public class SingletonThree {
    	//volatile指令关键字确保实例是线程安全的
    	private volatile static SingletonThree instance;
    	
    	private SingletonThree() {
    		
    	}
    	//双重检验锁实现 线程安全&延时加载
    	public static SingletonThree getInstance() {
    		if(instance == null) {
    			synchronized (SingletonThree.class) {
    				if(instance == null) {
    					instance = new SingletonThree();
    				}
    			}
    		}
    		return instance;
    	}
    	
    }
    

      四.静态内部类

          1.静态初始化,天然的线程安全,效率高。
          2.实现延时加载
          3.但是能用反射机制和序列化破解

    package com.wxisme.singleton;
    /**
     * 静态内部类实现单例模式
     * @author wxisme
     *
     */
    
    public class SingletonFour {
    	//静态内部类
    	private static class Inner {
    		private static final SingletonFour instance = new SingletonFour();
    	}
    	
    	private SingletonFour() {}
    	
    	//只有显示调用getInstance方法时才会加载内部类
    	public static final SingletonFour getInstance() {
    		return Inner.instance;
    	}
    
    }
    

     五.枚举

        1.天然的线程安全,效率高
        2.代码简洁。
        3.防止反射和反序列化破解

    package com.wxisme.singleton;
    /**
     * 枚举实现单例模式
     * @author wxisme
     *
     */
    
    public enum SingletonFive {
    	INSTANCE;
    	
    	public void getInstance() {
    		
    	}
    }
    

     存在的问题:以上方法中除了枚举的方式之外,都可以通过反射和反序列化的方式来破解。

     来破解一下。

     

    package com.wxisme.singleton;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Constructor;
    
    /**
     * 反射和反序列化破解单例模式(除枚举之外的都可以破解)
     * @author wxisme
     *
     */
    @SuppressWarnings("all")
    public class SingletonBreak {
    	
    	/*
    	 * 通过反射破解,以懒汉式为例
    	 * 应对策略:在私有构造器中手动抛出异常
    	 */
    	public static void test1() throws Exception {
    		SingletonTow st1 = SingletonTow.getInstance();
    		SingletonTow st2 = SingletonTow.getInstance();
    		System.out.println(st1==st2);
    		
    		//反射破解
    		
    		Class<SingletonTow> clazz = (Class<SingletonTow>) Class.forName("com.wxisme.singleton.SingletonTow");
            //获取构造器
    		Constructor<SingletonTow> c = clazz.getDeclaredConstructor(null);
    		c.setAccessible(true);//跳过权限的检查,可以访问私有构造器
    		SingletonTow st3 = c.newInstance();
    		SingletonTow st4 = c.newInstance();
    		System.out.println(st3==st4);
    	}
    	
    	/*
    	 * 反序列化破解 以懒汉式为例  (被反序列化的类必须实现Serializable接口)
    	 * 应对策略:在类中定义一个readResolve()方法,当反序列化时直接返回已经存在的对象
    	 */
    	public static void test2() throws IOException, ClassNotFoundException {
    		SingletonTow st1 = SingletonTow.getInstance();
    		SingletonTow st2 = SingletonTow.getInstance();
    		System.out.println(st1==st2);
    		
    		//序列化st1对象
    		FileOutputStream fos = new FileOutputStream("e:/a.txt");
    		ObjectOutputStream oos = new ObjectOutputStream(fos);
    		oos.writeObject(st1);
    		oos.close();
    		fos.close();
    		
    		//反序列化创建对象
    		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/a.txt"));
    		SingletonTow st3 = (SingletonTow) ois.readObject();
    		ois.close();
    		
    		System.out.println(st1==st3);
    		
    		
    		
    	}
    	public static void main(String[] args) throws Exception {
    		test1();
    		System.out.println("---------------");
    		test2();
    		
    		
    	}
    }
    

       不过这种破解可以防止,但是比较繁琐。

       防止反射破解的方法:

      在私有构造器中手动抛出异常

    private SingletonTow() {
    	//防止反射破解
    		
    	if(instance != null) {
    		throw new RuntimeException();
    	}
    		
    		
    	}
    

      添加一个getClass()方法

     

    private static Class getClass(String classname)
                throws ClassNotFoundException {
    		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();   
    
    		if(classLoader == null)   
    			classLoader = SingletonTow.class.getClassLoader();   
    
    		return (classLoader.loadClass(classname)); 
    

        防止反序列化破解的方法:

        添加一个readRsolve()方法

    private Object readResolve() {
    		return instance;
    	}
    

       

     总结:通过以上可以得出结论:如果需要延时加载,静态内部类好于懒汉式,不需要延时加载则枚举好于饿汉式。双重检验锁是对饿汉式的优化但是不推荐使用。如果没有特别的安全要求静态内部类式是最好的,如果需要还可以防止破解。懒汉式也不错。

     PS.在多线程环境下测试每种方式的执行效率。(感谢高淇老师的视频:))

          必须在除main线程执行其他所有的线程执行完之后才能计时,用到了CountDownLatch类来控制。

         Demo:

        

    package com.wxisme.singleton;
    
    import java.util.concurrent.CountDownLatch;
    
    /**
     * 在多线程环境下测试单例模式的效率
     * @author wxisme
     *
     */
    public class TestEfficiency {
    	public static void main(String[] args) {
    		long start = System.currentTimeMillis();
    		int thread = 0;
    		//内部类方法生命周期和全局变量不一致,需要加final
    		final CountDownLatch count = new CountDownLatch(thread);
    		for(int i=0; i<100; i++) {
    			new Thread(new Runnable() {
    
    				@Override
    				public void run() {
    					for(int j=0; j<10000; j++) {
    						Object o = SingletonOne.getInstance();
    					}
    					count.countDown();//一个线程执行完计数器减一。
    				}
    				
    			}).start();
    			
    		}
    		try {
    			count.await();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}//阻塞main线程,直到所有的线程执行完,线程计数器减为零。
    		long end = System.currentTimeMillis();
    		
    	}
    
    }
    

         


     

  • 相关阅读:
    Android中传感器的基本概念
    Android攻城狮 multi-touch多点触摸
    Android攻城狮SurfaceView
    Android攻城狮Tab类型
    Android攻城狮使用SubMenu创建子菜单
    Android攻城狮认识ContextMenu
    Android攻城狮OptionsMenu
    Android攻城狮Notification实现状态通知栏
    虹软人脸识别SDK(java+linux/window)
    人脸识别ArcFace C#DEMO 开发应用全过程
  • 原文地址:https://www.cnblogs.com/wxisme/p/4517343.html
Copyright © 2011-2022 走看看