设计模式——单例模式

  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();
		
	}

}

 

 

 

     

 

 

 

 

 

 


 

posted @ 2015-05-20 15:51  Pickle  阅读(599)  评论(0编辑  收藏  举报