单例模式的优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要 比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理 • 常见的五种单例模式实现方式: – 主要: • 饿汉式(线程安全,调用效率高。 但是,不能延时加载。) • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。) – 其他: • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用) • 静态内部类式(线程安全,调用效率高。 但是,可以延时加载) • 枚举单例(线程安全,调用效率高,不能延时加载)
饿汉式实现(单例对象立即加载)
要点: 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!
代码语言:javascript复制package com.ahzy;
/** * 单例饿汉式 * @author 晓宇码匠 * 频繁的调用这个实例的时候用饿汉式 */
public class SingletonDome01 {
//类初始化时加载这个对象,没有延迟加载的优势,加载类时,是天然的线程安全
private static SingletonDome01 instance = new SingletonDome01();
//私有化构造器
private SingletonDome01(){
}
//方法没有同步,调用效率高
public static SingletonDome01 getInstance() {
return instance;
}
}
懒汉式实现(单例对象延迟加载)
要点: lazy load! 延迟加载, 懒加载! 真正用的时候才加载! 问题:资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。
代码语言:javascript复制package com.ahzy;
/** * 单例懒汉式 * @author 晓宇码匠 * 资源利用率高。但是每次调用getIntance()都要同步,并发效率低 * 当创建实例的代价较大时,用懒汉式 */
public class SingletonDome02 {
private static SingletonDome02 instance;
private SingletonDome02() {
}
//方法同步,调用效率低
public static synchronized SingletonDome02 getInstance(){
//延迟加载,懒加载,真正用的时候再加载
if(instance==null){
instance = new SingletonDome02();
}
return instance;
}
}
双重检测锁实现
要点:这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。 问题: 由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
代码语言:javascript复制package com.ahzy;
/** * 单例双重检测锁模式 * @author 晓宇码匠 * 问题:由于编译器优化原因和JVM底层模式内部原因,偶尔会出现数据调整问题,不建议使用 */
public class SingletonDome03 {
private static SingletonDome03 instance = null;
private SingletonDome03() {
}
/* * 这个模式将同步内容下方到if内部,提高了执行的效率 * 不必每次获得对象时都要进行同步,只有第一次才同步 * 创建了以后就没必要了。 */
public static SingletonDome03 getInstance() {
if (instance == null) {
SingletonDome03 sc;
synchronized (SingletonDome03.class) {
sc = instance;
if (sc == null) {
synchronized (SingletonDome03.class) {
if (sc == null) {
sc = new SingletonDome03();
}
}
instance=sc;
}
}
}
return instance;
}
}
静态内部类实现方式(也是一种懒加载方式)
要点:
- 外部类没有static属性,则不会像饿汉式那样立即加载对象。
- 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
- 兼备了并发高效调用和延迟加载的优势!
package com.ahzy;
/** * 静态类部类(也是一种懒加载) * @author 晓宇码匠 * 特点:延迟加载,调用效率高,线程安全 */
public class SingletonDome04 {
private static class SingletClassInstance{
private static SingletonDome04 instance = new SingletonDome04();
}
private SingletonDome04(){
}
public static SingletonDome04 getInstance(){
return SingletClassInstance.instance;
}
}
枚举实现单例模式
优点:
- 实现简单.
- 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
缺点:无延迟加载.
代码语言:javascript复制package com.ahzy;
/** * 单例枚举式 * @author 晓宇码匠 * 优点:简单 * 缺点:没有延迟加载 */
public enum SingletonDome05 {
//这个枚举元素,本身就是单例模式
INSTANCE;
//添加自己需要的操作
public void singletonOperation() {
}
}
测试
代码语言:javascript复制package com.ahzy;
public class Client {
public static void main(String[] args) {
SingletonDome01 s1 = SingletonDome01.getInstance();
SingletonDome01 s2 = SingletonDome01.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(SingletonDome05.INSTANCE==SingletonDome05.INSTANCE);
}
}
结果:
代码语言:javascript复制com.ahzy.SingletonDome01@15db9742
com.ahzy.SingletonDome01@15db9742
true
破解单例
方法:反射和反序列化(不包含枚举式) 预防操作(这个一般会在开发jdk或一些jar包的时候会去用):
- 反射可以破解上面几种(不包含枚举式)实现方式!(可以在构造方法中手动抛出异常控制)
- 可以通过定义readResolve()防止获得不同对象。
- 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
public class SingletonDemo01 implements Serializable {
private static SingletonDemo01 s;
private SingletonDemo01() throws Exception {
if (s != null) {
throw new Exception("只能创建一个对象");
// 通过手动抛出异常,避免通过反射创建多个单例对象!
}
} // 私有化构造器
public static synchronized SingletonDemo01 getInstance() throws Exception {
if (s == null) {
s = new SingletonDemo01();
}
return s;
}
// 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
private Object readResolve() throws ObjectStreamException {
return s;
}
}
总结
- 效率
模式 | 时间 |
---|---|
饿汉式 | 22ms |
静态内部类式 | 28ms |
枚举式 | 32ms |
双重检查锁式 | 65ms |
懒汉式 | 636ms |
- 比较与特点 主要: • 饿汉式(线程安全,调用效率高。 但是,不能延时加载。) • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。) 其他: • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用) • 静态内部类式(线程安全,调用效率高。 但是,可以延时加载) • 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)
- 如何选用? 单例对象 占用 资源 少,不需要 延时加载:枚举式 好于 饿汉式。 单例对象 占用 资源 大,需要 延时加载: 静态内部类式 好于 懒汉式。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/106710.html原文链接:https://javaforall.cn