博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
GOF设计模式之1:单例设计模式
阅读量:5126 次
发布时间:2019-06-13

本文共 7900 字,大约阅读时间需要 26 分钟。

1.单例设计模式核心作用:

保证一个类只有一个实例,并且提供了访问该实例的全局访问点

2.常见应用场景:

  • window的任务管理器
  • 项目中读取配置文件一般也是一个单例模式
  • 数据库连接池的设计也是采用单例模式,因为数据库连接是一种数据库资源
  • 操作系统的文件管理系统,也是单例模式,一个操作系统只能有一个文件系统
  • Application也是单例的应用(Servlet编程或者Android的Application类)
  • 在Spring中,每个bean默认也是单例的,这样的有点儿事Spring容器可以管理
  • 在Servlet编程中每个Servlet也是单例的
  • 在Spring MVC和Struts1框架中控制器对象也是单例

3.单例模式的优点

  • 由于单例模式只生产一个对象,减少了系统开销,当一个对象的产生需要的资源比较多的时候,比如读取配置文件、产生其它依赖对象时,则可以在应用启动的时候直接产生一个单例对象,然后永久驻存内存的方式来解决。
  • 单例模式可以在系统设置全局访问点,优化共享资源的访问。例如可以设计一个单例类,负责所有数据表的映射。

4.常见5中单例模式的实现方式:

主要

饿汉式:线程安全,调用效率高。但是不能延时加载

懒汉式:线程安全,调用效率不高。但是可以延迟加载

其它:

双重检锁式:由于JVM底层内部模型的原因,偶尔会出现问题,不建议使用

静态内部类式:线程安全,调用效率高,而且可以延迟加载

枚举单例:线程安全,调用效率高,不可延迟加载

饿汉式的示例代码:

public class Singleton01 {    //类初始化的时候,立即加载这个对象(没有延时加载的优势)。加载类时,是线程安全的    private static Singleton01 instance = new Singleton01();    private Singleton01(){}    //方法没有同步调用效率高    public static Singleton01 getInstance(){        return instance;    }}

 

饿汉式单例模式的代码中,static变量会在类装载的时候进行初始化,此时不会涉及到多个线程对象访问该对象的问题。虚拟机会保证只会装载一次该类,肯定不会发生并发访问的问题,因此可以省略synchronized关键字

问题:如果仅仅是加载本类,而不是要调用getInstance,甚至永远都没有调用,则会造成资源浪费。

懒汉式的示例代码

1 package com.chunjiangchao.pattern.singleton; 2 /** 3  * 测试懒汉式单例模式 4  */ 5 public class Singleton02 { 6     //类初始化的时候,不初始化这个对象(延时加载,真正用的时候再创建)。 7     private static Singleton02 instance = null; 8     private Singleton02(){} 9     方法同步,调用效率低!10     public static synchronized Singleton02 getInstance(){11         if(instance == null)12             instance = new Singleton02();13         return instance;14     }15 }

 

要点:延迟加载,懒加载真正用到的时候才会选择加载

问题:

资源利用率高了,但是每次调用getInstance()方法都要同步,并发效率较低。

双重检锁实现

1 package com.chunjiangchao.pattern.singleton; 2 /** 3  * 测试DCL(双重检锁)单例模式 4  *  5  */ 6 public class Singleton03 { 7     //类初始化的时候,不初始化这个对象(延时加载,真正用的时候再创建)。 8     private volatile static Singleton03 instance = null; 9     private Singleton03(){}10     代码块同步,调用效率要比同步方法要快一些,由于JVM的原因在高并发的情况下会出现问题11     public static  Singleton03 getInstance(){12         if(instance == null){13             synchronized (Singleton03.class) {14                 if(instance == null)15                     instance = new Singleton03();16             }17         }18         return instance;19     }20 }

 Volatile关键字的作用:

  • 防止指令重排序如:instance = new Singleton03();这条操作分三步执行,1、分配内存;2、进行初始化;3、将生成对象的堆内存地址赋值给instance变量。这些指令中2、 3的位置可能会进行重排序,导致在获取到对象的时候,该对象还没有进行初始化。volatitle可以防止这种指令进行重排序。
  • 当然Volatile还有一个作用是同步CPU缓存区和内存中的变量

提高了执行 的效率,不必每次获取对象的时候都要进行同步,只有第一次才会进行同步创建。

问题:

由于编译器优化的原因和JVM底层内部模型原因,偶尔会出现问题,不建议使用。但是我们可以在instance前面添加volatile关键字,这样就没问题了。

静态内部类实现方式:(懒加载方式)

1 package com.chunjiangchao.pattern.singleton; 2 /** 3  * 静态内部类单例模式 4  * 这种方式:线程安全,调用效率高,并且实现了延时加载! 5  */ 6 public class Singleton04 { 7     private Singleton04(){} 8     public static  Singleton04 getInstance(){ 9         return Inner.instance;10     }11     private static class Inner{12         private static final Singleton04 instance = new Singleton04();13     }14 }

 

外部类没有static属性,则不会像饿汉式那样,上来就把对象造出来了

只有真正调用getInstance才会加载静态内部类。加载类时是线程安全的。instance 是static final类型,保证了内存中只有这样一个实例存在,而且只被赋值一次,从而保证了线程安全性。

兼并并发高效调用和延迟加载的优势。

换一句户说:静态内部有具备饿汉式和延迟加载的优势。

枚举实现单例:

1 package com.chunjiangchao.pattern.singleton; 2 /** 3  * 枚举式实现单例模式(没有延时加载) 4  */ 5 public enum Singleton05 { 6     instance;//这个枚举元素,本身就是单例对象! 7     public void operation(){ 8         //添加需要的操作 9     }10 }

 

优点:实现简单;枚举本身就是单例。由JVM从根本上提供保障。避免反射和序列化的漏洞

缺点:无延迟加载

5.如何选用这五种单例模式?

单例对象占用资源少,不需要延迟加载:

枚举好于饿汉式

单例对象占用资源大,需要延迟加载

静态内部类好于懒汉式

6.问题

反射可以破解上面(不包含枚举)的实现方式(防止的做法是在构造方法中手动抛出异常)

反序列化可以破解(不包含枚举)的实现方式

可以通过定义readResolve防止获得不同对象。反序列化的时候,如果对象所在的类定义了readResolve()方法(一种回调方法),返回自己创建的那个对象。

示例代码如下:

 

1 package com.bjsxt.singleton; 2  3 import java.io.ObjectStreamException; 4 import java.io.Serializable; 5  6 /** 7  * 测试懒汉式单例模式(如何防止反射和反序列化漏洞) 8  * 9  */10 public class SingletonDemo6 implements Serializable {11     //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。12     private static SingletonDemo6 instance;  13     14     private SingletonDemo6(){ //私有化构造器15         if(instance!=null){16             throw new RuntimeException();17         }18     }19     20     //方法同步,调用效率低!21     public static  synchronized SingletonDemo6  getInstance(){22         if(instance==null){23             instance = new SingletonDemo6();24         }25         return instance;26     }27     28     //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!29     private Object readResolve() throws ObjectStreamException {30         return instance;31     }32     33 }

 测试代码如下:

1 package com.chunjiangchao.pattern.singleton; 2  3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutputStream; 8 import java.lang.reflect.Constructor; 9 10 /**11  * 测试单例模式12  */13 public class SingletonDemo {14     public static void main(String[] args) throws Exception {15         Singleton01 singleton01 = Singleton01.getInstance();16         Singleton01 singleton02 = Singleton01.getInstance();17         System.out.println(singleton01.hashCode());18         System.out.println(singleton02.hashCode());19         20         /**21         //测试反射22         try {23             Class
clazz = Class.forName("com.chunjiangchao.pattern.singleton.Singleton06");24 Constructor
constructor = clazz.getDeclaredConstructor(null);25 constructor.setAccessible(true);26 Singleton06 singleton06 = (Singleton06) constructor.newInstance(null);27 System.out.println(singleton06);28 System.out.println(Singleton06.getInstance());29 } catch (Exception e) {30 e.printStackTrace();31 }32 //打印的结果:33 //com.chunjiangchao.pattern.singleton.Singleton06@495c83b234 //com.chunjiangchao.pattern.singleton.Singleton06@58ca40be35 */36 //通过序列化获取一个对象37 Singleton06 instance = Singleton06.getInstance();38 FileOutputStream fos = new FileOutputStream("/Users/xxx/Desktop/a.txt");39 ObjectOutputStream oos = new ObjectOutputStream(fos);40 oos.writeObject(instance);41 oos.close();42 fos.close();43 44 FileInputStream fis = new FileInputStream("/Users/xxx/Desktop/a.txt");45 ObjectInputStream ois = new ObjectInputStream(fis);46 Object obj = ois.readObject();47 ois.close();48 fis.close();49 System.out.println(instance);50 System.out.println(obj);51 }52 53 }

 7、测试几种模式的效率

五种单例模式在多线程环境下效率测试:使用CountDownLatch同步工具类,允许当前线程等待其它一组线程都执行完毕后,执行当前线程的后续操作。

countDown()当前线程执行此方法,计数器-1

await()调动此方法会一直阻塞当前线程,知道计数器为0的时候重新运行当前线程

下面示例代码来演示当前线程执行的效率:

1 package com.chunjiangchao.pattern.singleton; 2  3 import java.util.concurrent.CountDownLatch; 4  5 /** 6  * 五种单例模式的性能测试 7  * 8  */ 9 public class SingletonDemo02 {10 11     public static void main(String[] args) throws InterruptedException {12         int threadNum = 10;13         long beginTime = System.currentTimeMillis();14         final CountDownLatch countDownLatch = new CountDownLatch(threadNum);15         for (int i = 0; i < threadNum; i++) {16             new Thread(new Runnable() {17                 18                 @Override19                 public void run() {20                     for (int j = 0; j < 20000; j++) {21                         //Singleton01.getInstance();//18ms22                         //Singleton02.getInstance();//49ms23                         //Singleton03.getInstance();//22ms24                         //Singleton04.getInstance();//32ms25                         Singleton05 instance = Singleton05.instance;//9ms26                     }27                     countDownLatch.countDown();28                 }29             }).start();30         }31         //让主线程进行阻塞32         countDownLatch.await();33         long endTime = System.currentTimeMillis();34         System.out.println(endTime-beginTime);35     }36 37 }

单例对象占用资源少,不需要延迟加载:

转载于:https://www.cnblogs.com/chun-jiang-chao-de-gu-shi/p/5376574.html

你可能感兴趣的文章
Mac远程连接服务器
查看>>
使用python爬取东方财富网机构调研数据
查看>>
贝叶斯理论基础理解
查看>>
2018java最新面试题
查看>>
PHP编写命令行脚本和后台运行程序的注意事项
查看>>
php 换行 PHP_EOL变量
查看>>
JS中关于clientWidth、offsetWidth、scrollWidth
查看>>
Telerik Reporting之生成报表
查看>>
不要怂!就是干!
查看>>
11.8学习笔记
查看>>
[BZOJ 5074] 小B的数字
查看>>
Java并发编程笔记之ThreadLocal内存泄漏探究
查看>>
BZOJ5020: [THUWC 2017]在美妙的数学王国中畅游(LCT,泰勒展开,二项式定理)
查看>>
Java并发编程笔记之Semaphore信号量源码分析
查看>>
Asp.net--DropDownList控件绑定数据库数据
查看>>
(IOS)截图Demo
查看>>
[原创]mybatis中整合ehcache缓存框架的使用
查看>>
尚未注册 OLE DB 访问接口 "SQLNCLI10" 7043 错误
查看>>
c# list中的removeAll方法 两个List集合去除交集部分
查看>>
团队冲刺第一天
查看>>