目录
-
- 目录
- 常见使用场景和优点
- 1 使用场景
- 2 优点
- 单例实现方式
- 1 五种常见的实现方式
- 2 如何选择
- 3代码实现
- 31饿汉式
- 32懒汉式
- 33双重检测锁式
- 34静态内部类式
- 35枚举式
- 通过反射和反序列化破解单例模式
- 借助CountDownLatch类多线程效率测试
1. 常见使用场景和优点
1.1 使用场景
- 项目中的配置文件,读取配置文件的类, 一般也只有一个対象没有必要每次使用配置文件数握 , 每次new一个对象去读取
- 数据库的连接池
- Spring的bean配置默认是单例
- SpringMVC和Struts,Servlet中控制层都是单例
1.2. 优点
单例只产生一个实例,减少了系统性能的开销,当一个对象产生需要比较多的资源时可以考虑使用单例设计模式。
2.单例实现方式
2.1. 五种常见的实现方式
- 饿汉式(线程安全,调用效率高,但是不能延时加载)
- 懒汉式(线程安全,调用效率不高,但是可以延时加载)
- 双层检测锁机制(用于JVM底层内部模型原因,偶尔会出问题,不建议使用)
- 静态内部类(线程安全,调率效率高,可以延时加载)
- 枚举单例(线程安全,调用效率高,不能延时加载)
2.2. 如何选择
单例对象 占用资源少,不需要延时加载:
- 枚举式(好过饿汉式)
单例对象 占用资源大,需要延时加载:
- 静态内部类式(好过懒汉式)
2.3.代码实现
2.3.1.饿汉式
/*** 测试饿汉式单例模式*/
public class SingletonDemo1 {
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1(){}//方法没有同步,调用效率高!public static SingletonDemo1 getInstance(){return instance;}}
2.3.2.懒汉式
/*** 测试懒汉式单例模式*/
public class SingletonDemo2 {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。private static SingletonDemo2 instance; private SingletonDemo2(){ //私有化构造器}
//方法同步,调用效率低!public static synchronized SingletonDemo2 getInstance(){if(instance==null){instance = new SingletonDemo2();}return instance;}}
2.3.3.双重检测锁式
/*** 双重检查锁实现单例模式**/
public class SingletonDemo3 {
private static SingletonDemo3 instance = null; public static SingletonDemo3 getInstance() { if (instance == null) { SingletonDemo3 sc; synchronized (SingletonDemo3.class) { sc = instance; if (sc == null) { synchronized (SingletonDemo3.class) { if(sc == null) { sc = new SingletonDemo3(); } } instance = sc; } } } return instance; } private SingletonDemo3() { } }
2.3.4.静态内部类式
/*** 测试静态内部类实现单例模式* 这种方式:线程安全,调用效率高,并且实现了延时加载!**/
public class SingletonDemo4 {
private static class SingletonClassInstance {
private static final SingletonDemo4 instance = new SingletonDemo4();}private SingletonDemo4(){}//方法没有同步,调用效率高!public static SingletonDemo4 getInstance(){return SingletonClassInstance.instance;}}
2.3.5.枚举式
/*** 测试枚举式实现单例模式(没有延时加载)*/
public enum SingletonDemo5 {//这个枚举元素,本身就是单例对象!INSTANCE;//添加自己需要的操作!public void singletonOperation(){}}
3. 通过反射和反序列化破解单例模式
/*** 测试反射和反序列化破解单例模式*/
public class Client2 {
public static void main(String[] args) throws Exception {SingletonDemo6 s1 = SingletonDemo6.getInstance();SingletonDemo6 s2 = SingletonDemo6.getInstance();System.out.println(s1);System.out.println(s2);//通过反射的方式直接调用私有构造器
// Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.bjsxt.singleton.SingletonDemo6");
// Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
// c.setAccessible(true);
// SingletonDemo6 s3 = c.newInstance();
// SingletonDemo6 s4 = c.newInstance();
// System.out.println(s3);
// System.out.println(s4);//通过反序列化的方式构造多个对象 FileOutputStream fos = new FileOutputStream("d:/a.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s1);oos.close();fos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();System.out.println(s3);}
}
/*** 测试懒汉式单例模式(如何防止反射和反序列化漏洞)**/
public class SingletonDemo6 implements Serializable {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。private static SingletonDemo6 instance; private SingletonDemo6(){ //私有化构造器if(instance!=null){throw new RuntimeException();}}//方法同步,调用效率低!public static synchronized SingletonDemo6 getInstance(){if(instance==null){instance = new SingletonDemo6();}return instance;}//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!private Object readResolve() throws ObjectStreamException {return instance;}}
4. 借助CountDownLatch类多线程效率测试
/*** 测试多线程环境下五种创建单例模式的效率*/
public class Client3 {
public static void main(String[] args) throws Exception {long start = System.currentTimeMillis();int threadNum = 10;final CountDownLatch countDownLatch = new CountDownLatch(threadNum);for(int i=0;i<threadNum;i++){new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<1000000;i++){
// Object o = SingletonDemo4.getInstance();Object o = SingletonDemo5.INSTANCE;}countDownLatch.countDown();}}).start();}countDownLatch.await(); //main线程阻塞,直到计数器变为0,才会继续往下执行!long end = System.currentTimeMillis();System.out.println("总耗时:"+(end-start));}
}
方式 | time/ms |
---|---|
饿汉式 | 22 |
懒汉式 | 636 |
静态内部类式 | 28 |
枚举式 | 32 |
双重检查锁式 | 65 |