• 设计模式(一)单例模式


    单例模式:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。


    优点:1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时产生一个单例对象,然后永久驻留内存的方式来解决。

            2. 单例模式可以在系统设置全局的访问点,例如可以设计一个单例类,负责所有数据表的映射处理。

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

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

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

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

    静态内部类式:线程安全,调用效率高。但是,可以延时加载。

    枚举单例:线程安全,调用效率高,不能延时加载。

    饿汉式:

    1 public class Singleton{
    2     private static Singleton singleton = new Singleton();
    3     private Singleton(){}
    4     public static Singleton getInstance(){
    5         return singleton;
    6     }
    7 }

    懒汉式:

     1 public class Singleton{
     2     private static Singleton singleton;
     3     private Singleton(){}
     4     public synchronized static Singleton getInstance(){
     5         if(singleton == null){
     6             singleton = new Singleton();
     7         }
     8         return singleton;
     9     }
    10 }

    双重检测锁式:

     1 public class Singleton{
     2     private static Singleton singleton;
     3     private Singleton(){}
     4     public static Singleton getInstance(){
     5         if(singleton == null){
     6             synchronized(Singleton.class){
     7                 if(singleton == null){
     8                     singleton = new Singleton();
     9                 }
    10             }
    11         }
    12         return singleton;
    13     }
    14 }

    静态内部类式:

    1 public class Singleton{
    2     private static class SingletonClassInstance{
    3         private static final Singleton singleton = new Singleton();
    4     }
    5     public static Singleton getInstance(){
    6         return SingletonClassInstance.singleton;
    7     }
    8     private Singleton(){}
    9 }

    注:外部类没有static属性,则不会像饿汉式那样立即加载对象。只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。singleton是static final(final可省略)类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。兼备了并发高效调用和延迟加载的优势。

    枚举单例:

     1 public enum Singleton{
     2     //定义一个枚举的元素,它就代表了Singleton的一个实例
     3     INSTANCE;
     4     public void singletonOperation(){
     5         //功能处理
     6     }
     7 }
     8 
     9 public class GOF {
    10     public static void main(String[] args){
    11         Singleton s1 = Singleton.INSTANCE;
    12         Singleton s2 = Singleton.INSTANCE;
    13         System.out.println(s1==s2);
    14     }
    15 }
    16 
    17 打印结果:true

    注:实现简单。枚举本身就是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化的漏洞。但无延迟加载。

    五种如何选用:

    单例对象 占用资源少,不需要延时加载:枚举式好于饿汉式

    单例对象 占用资源大,需要延时加载:静态内部类好于懒汉式

    问题

    反射和反序列化可以破解上面几种(不包含枚举式)实现方式。

    1. 例:通过反射的方式直接调用私有构造器。

    代码中的SingletonDemo1为饿汉式

     1 import java.lang.reflect.Constructor;
     2 
     3 public class SingletonDemo1_1 {
     4     public static void main(String[] args)throws Exception{
     5         SingletonDemo1 s1 = SingletonDemo1.getInstance();
     6         SingletonDemo1 s2 = SingletonDemo1.getInstance();
     7         System.out.println(s1 == s2);
     8         
     9         Class<SingletonDemo1> clazz = (Class<SingletonDemo1>)SingletonDemo1.class;
    10         Constructor<SingletonDemo1> c = clazz.getDeclaredConstructor(null);
    11         c.setAccessible(true);
    12         SingletonDemo1 s3 = c.newInstance();
    13         SingletonDemo1 s4 = c.newInstance();
    14         System.out.println(s3 == s4);
    15     }
    16 }
    17 
    18 打印结果:
    19 true
    20 false

    解决方法:可以在构造方法中抛出异常控制。

    修改后的饿汉式为:

     1 public class SingletonDemo1{
     2     private static SingletonDemo1 single = new SingletonDemo1();
     3     private SingletonDemo1(){
     4         if(single != null){
     5             throw new RuntimeException();
     6         }
     7     }
     8     public static SingletonDemo1 getInstance(){
     9         return single;
    10     }
    11 }

    2. 例:通过反序列化的方式构造多个对象。

     1 import java.io.FileInputStream;
     2 import java.io.FileOutputStream;
     3 import java.io.ObjectInputStream;
     4 import java.io.ObjectOutputStream;
     5 
     6 public class SingletonDemo1_1 {
     7     public static void main(String[] args)throws Exception{
     8         SingletonDemo1 s1 = SingletonDemo1.getInstance();
     9         SingletonDemo1 s2 = SingletonDemo1.getInstance();
    10         System.out.println(s1 == s2);
    11         
    12         FileOutputStream fos = new FileOutputStream("G:/a.txt");
    13         ObjectOutputStream oos = new ObjectOutputStream(fos);
    14         oos.writeObject(s1);
    15         oos.close();
    16         fos.close();
    17         
    18         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("G:/a.txt"));
    19         SingletonDemo1 s3 = (SingletonDemo1)ois.readObject();
    20         System.out.println(s2 == s3);
    21     }
    22 }
    23 
    24 打印结果:
    25 true
    26 false

    解决方法:定义readResolve()方法,则直接返回此方法指定的对象,而不需要单独再创建新对象。

    如:

     1 import java.io.ObjectStreamException;
     2 import java.io.Serializable;
     3 
     4 public class SingletonDemo1 implements Serializable {
     5     private static SingletonDemo1 single = new SingletonDemo1();
     6     private SingletonDemo1(){
     7         if(single != null){
     8             throw new RuntimeException();
     9         }
    10     }
    11     public static SingletonDemo1 getInstance(){
    12         return single;
    13     }
    14     private Object readResolve() throws ObjectStreamException{
    15         return single;
    16     }
    17 }

    五种单例模式在多线程环境下的效率测试:

    CountDownLatch同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

    countDown() 当前线程调此方法,则计数减一(建议放在finally里执行)

    await() 调用此方法会一直阻塞当前线程,直到计时器的值为0

     1 import java.util.concurrent.CountDownLatch;
     2 
     3 
     4 public class Cilent {
     5     public static void main(String[] args)throws Exception{
     6         long start = System.currentTimeMillis();
     7         int threadNum = 10;
     8         final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
     9         for(int i = 0; i < 10; i++){
    10             new Thread(new Runnable(){
    11                 @Override
    12                 public void run() {
    13                     for(int i = 0; i < 100000; i++){
    14                         Object o = SingletonDemo1.getInstance();
    15                     }
    16                     countDownLatch.countDown();
    17                 }
    18             }).start();
    19         }
    20         countDownLatch.await();
    21         long end = System.currentTimeMillis();
    22         System.out.println("总耗时 = " + (end - start));
    23     }
    24 }

    经过测试 懒汉式耗时最多,其次是双重检查锁式,其他相差不大。

  • 相关阅读:
    [技术项目4]--接口自动化数据一览项目总结
    [技术项目3]--流量回放项目总结
    [技术项目2]--禅道项目报告统计总结
    [技术项目1]--数据工厂项目总结
    自动化测试常用的框架
    【vue】element-表单中,下拉框选中某个值后,同步更新其他输入框的值
    【vue】vue打包后,将dist文件夹自动压缩成生成dist.zip压缩包--filemanager-webpack-plugin
    译文:ovs+dpdk中的“vHost User NUMA感知”特性
    vue中防抖,节流的使用
    横向结构的树组件(leader-line-vue)
  • 原文地址:https://www.cnblogs.com/zhangtianq/p/5936978.html
Copyright © 2020-2023  润新知