IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 设计模式---单例模式 -> 正文阅读

[Java知识库]设计模式---单例模式

定义:保证一个类中只有一个实例,保证提供一个全局访问点。

单例模式的特点:

  • 类构造器私有
  • 持有自己类的引用
  • 对外提供获取实例的静态方法

1.懒汉模式

在这里插入图片描述
在这里插入图片描述

2.饿汉模式

在这里插入图片描述
在这里插入图片描述

3.静态内部类

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.2反射攻击

先回顾下反射获取运行时类的构造方法:

  • getConstructors()返回所有public的构造器。

  • getDeclaredConstructors()返回所有private和public构造器。

  • getConstructor()返回指定参数类型public的构造器。

  • getDeclaredConstructor()返回指定参数类型的private和public构造器。

反射获取运行时类的属性:

  • getFields()返回所有public的字段。

  • getDeclaredFields()返回所有private和public字段。

  • getField()返回指定字段名public的字段。

  • getDeclaredField()返回指定字段名的private和public字段名。

如果其他人使用反射,依然能够通过类的无参构造方式创建对象。例如:

Class<SimpleSingleton5> simpleSingleton5Class = SimpleSingleton5.class;
try {
    SimpleSingleton5 newInstance = simpleSingleton5Class.newInstance();
    System.out.println(newInstance == SimpleSingleton5.getInstance());
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

上面代码打印结果是false。由此看出,通过反射创建的对象,跟通过getInstance方法获取的对象,并非同一个对象,也就是说,这个漏洞会导致SimpleSingleton5非单例。

那么,要如何防止这个漏洞呢?
答:这就需要在无参构造方式中判断,如果非空,则抛出异常了。

public class SimpleSingleton5 {

    private SimpleSingleton5() {
        if(Inner.INSTANCE != null) {
           throw new RuntimeException("不能支持重复实例化");
       }
    }

    public static SimpleSingleton5 getInstance() {
        return Inner.INSTANCE;
    }

    private static class Inner {
        private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5();
        }
    }

}

3.3 反序列化漏洞

java中的类通过实现Serializable接口,可以实现序列化。我们可以把类的对象先保存到内存,或者某个文件当中。后面在某个时刻,再恢复成原始对象。

public class SimpleSingleton5 implements Serializable {

    private SimpleSingleton5() {
        if (Inner.INSTANCE != null) {
            throw new RuntimeException("不能支持重复实例化");
        }
    }

    public static SimpleSingleton5 getInstance() {
        return Inner.INSTANCE;
    }

    private static class Inner {
        private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5();
    }

    private static void writeFile() {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            SimpleSingleton5 simpleSingleton5 = SimpleSingleton5.getInstance();
            fos = new FileOutputStream(new File("test.txt"));
            oos = new ObjectOutputStream(fos);
            oos.writeObject(simpleSingleton5);
            System.out.println(simpleSingleton5.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    private static void readFile() {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(new File("test.txt"));
            ois = new ObjectInputStream(fis);
            SimpleSingleton5 myObject = (SimpleSingleton5) ois.readObject();

            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        writeFile();
        readFile();
    }
}

运行之后,发现序列化和反序列化后对象的hashCode不一样:
189568618
793589513

说明,反序列化时创建了一个新对象,打破了单例模式对象唯一性的要求。

那么如何解决这个问题呢?
重新readResolve方法。做法很简单,只需要在readResolve方法中,每次都返回唯一的Inner.INSTANCE对象即可。

private Object readResolve() throws ObjectStreamException {
    return Inner.INSTANCE;
}

290658609
290658609

4. 枚举类型Enum

其实在java中枚举就是天然的单例,每一个实例只有一个对象,这是java底层内部机制保证的。

public enum  SimpleSingleton7 {
    INSTANCE;
    
    public void doSamething() {
        System.out.println("doSamething");
    }
}   

jvm保证了枚举是天然的单例,并且不存在线程安全问题,此外,还支持序列化。单元素的枚举类型已经成为实现Singleton的最佳方法。

5.多例模式

允许创建多个实例。但它的初衷是为了控制实例的个数,其他的跟单例模式差不多。

public class SimpleMultiPattern {
    //持有自己类的引用
    private static final SimpleMultiPattern INSTANCE1 = new SimpleMultiPattern();
    private static final SimpleMultiPattern INSTANCE2 = new SimpleMultiPattern();

    //私有的构造方法
    private SimpleMultiPattern() {
    }
    //对外提供获取实例的静态方法
    public static SimpleMultiPattern getInstance(int type) {
        if(type == 1) {
          return INSTANCE1;
        }
        return INSTANCE2;
    }
}

多例模式和享元模式有什么区别:

  • 多例模式:跟单例模式一样,纯粹是为了控制实例数量,使用这种模式的类,通常是作为程序某个模块的入口。
  • 享元模式:它的侧重点是对象之间的衔接。它把动态的、会变化的状态剥离出来,共享不变的东西。

6. 使用场景

Runtime

jdk提供了Runtime类,我们可以通过这个类获取系统的运行状态。比如可以通过它获取cpu核数:

int availableProcessors = Runtime.getRuntime().availableProcessors();

Runtime类的关键代码如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
    ...
}

从上面的代码我们可以看出,这是一个单例模式,并且是饿汉模式。

NamespaceHandlerResolver

spring提供的DefaultNamespaceHandlerResolver是为需要初始化默认命名空间处理器,是为了方便后面做标签解析用的。

Nullable
private volatile Map<String, Object> handlerMappings;

private Map<String, Object> getHandlerMappings() {
  Map<String, Object> handlerMappings = this.handlerMappings;
  if (handlerMappings == null) {
   synchronized (this) {
    handlerMappings = this.handlerMappings;
    if (handlerMappings == null) {
     if (logger.isDebugEnabled()) {
      logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
     }
     try {
      Properties mappings =
        PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
      if (logger.isDebugEnabled()) {
       logger.debug("Loaded NamespaceHandler mappings: " + mappings);
      }
      handlerMappings = new ConcurrentHashMap<>(mappings.size());
      CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
      this.handlerMappings = handlerMappings;
     }
     catch (IOException ex) {
      throw new IllegalStateException(
        "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
     }
    }
   }
  }
  return handlerMappings;
 }

我们看到它使用了双重检测锁,并且还定义了一个局部变量handlerMappings,这是非常高明之处。

使用局部变量相对于不使用局部变量,可以提高性能。主要是由于 volatile 变量创建对象时需要禁止指令重排序,需要一些额外的操作。

spring 的单例

以前在spring中要定义一个bean,需要在xml文件中做如下配置:

<bean id="test" class="com.susan.Test" init-method="init" scope="singleton">

bean标签上有个scope属性,我们可以通过指定该属性控制bean实例是单例的,还是多例的。如果值为singleton,代表是单例的。当然如果该参数不指定,默认也是单例的。如果值为prototype,则代表是多例的。

在spring的AbstractBeanFactory类的doGetBean方法中:

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
      return createBean(beanName, mbd, args);
  });
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
    Object prototypeInstance = createBean(beanName, mbd, args);
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
    ....
}
  • 判断如果scope是singleton,则调用getSingleton方法获取实例。

  • 如果scope是prototype,则直接创建bean实例,每次会创建一个新实例。

  • 如果scope是其他值,则允许我们自定bean的创建过程。

其中getSingleton方法主要代码如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(beanName, "Bean name must not be null");
  synchronized (this.singletonObjects) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null) {
          singletonObject = singletonFactory.getObject();
         if (newSingleton) {
           addSingleton(beanName, singletonObject);
        }
   }
   return singletonObject;
  }
}

有个关键的singletonObjects对象,其实是一个ConcurrentHashMap集合:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  • 根据beanName先从singletonObjects集合中获取bean实例。
  • 如果bean实例不为空,则直接返回该实例。
  • 如果bean实例为空,则通过getObject方法创建bean实例,然后通过addSingleton方法,将该bean实例添加到singletonObjects集合中。
  • 下次再通过beanName从singletonObjects集合中,就能获取到bean实例了。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:22:50  更:2022-03-22 20:25:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:20:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码