java 设计模式之单例模式

简介

单例模式:一个类有且仅有一个实例,该类负责创建自己的对象,同时确保只有一个对象被创建。

特点:类构造器私有、持有自己实例、对外提供获取实例的静态方法。

单例模式的实现方式

饿汉式

类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

案例:

java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();
    
    private Singleton() { }
    
    public static Singleton getInstance() {
        return instance;
    }
}

饿汉式比较耗费资源,因为它创建单例的时间过早,它是在类被加载的时候创建单例的

懒汉式加双重检查加锁

饿汉式的优化,只有在获取类的实例时才会创建实例。

案例:

java 复制代码
public class SingleTon {
    // 使用volatile修饰,保证变量的可见性
    private static volatile SingleTon instance;

    public static SingleTon getInstance() {
        // 先检查实例是否存在,如果不存在才进入下面的同步块
        if (instance == null) {
            // 同步块,线程安全的创建实例
            synchronized (SingleTon.class) {
                // 再次检查实例是否存在,如果不存在才真的创建实例
                if (instance == null) {
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }
}

饿汉式中的注意事项:

1、为什么要用volatile修饰成员变量:为了保证指定变量的有序性和可见性。new一个对象的代码 SingleTon instance = new SingleTon(); 可以分解为3行伪代码:

text 复制代码
memory=allocate();// 分配内存 相当于c的malloc
ctorInstanc(memory) //初始化对象
instance=memory //设置instance指向刚分配的地址

上面的代码在编译器运行时,可能出现重排序,从 1-2-3 变为 1-3-2,在多线程环境下就会出现问题,用户拿到的实际上是没有初始化的对象,使用 volatile 关键字会禁止这种重排序。

2、为什么要双重锁:如果只有一个锁,很有可能两个线程都通过了 if(instance == null) 的判断,所以在进入同步代码块之后还需要再判断一次

用静态内部类来实现单例模式

案例:

java 复制代码
public class SingleTon {
    private SingleTon2() { }
    
    // 用一个私有的静态内部类来存储外部类的实例,类只会被加载一次,保证单例。
    // 内部类只有在被调用时才会被加载,保证了延迟加载
    private static class SingleTonHolder {
        private static SingleTon2 instance = new SingleTon2();
    }
    
    public static SingleTon2 getInstance() {
        return SingleTonHolder.instance;
    }
}

破坏单例模式

破坏单例模式:序列化和反射可以破坏单例模式。

  • 解决序列化破坏单例的问题:在类中添加readResolve方法,返回类中的实例,可以解决通过序列化破坏单例模式的问题
  • 解决反射破坏单例的问题:在构造方法中抛异常,可以解决通过反射破坏单例模式的问题

单例模式的使用案例

饿汉式单例模式的使用:jdk中的Runtime类,每个java程序中都只有一个Runtime实例,它代表java程序的运行环境

java 复制代码
public class Runtime {
    // 类被加载时,就会实例化一个对象并交给自己的引用
    private static Runtime currentRuntime = new Runtime();
 
    // 返回单例对象的方法
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    // 私有化的构造方法
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
}
相关推荐
不能隔夜的咖喱8 分钟前
牛客网刷题(2)
java·开发语言·算法
serve the people14 分钟前
python环境搭建 (十二) pydantic和pydantic-settings类型验证与解析
java·网络·python
lekami_兰29 分钟前
Java 并发工具类详解:4 大核心工具 + 实战场景,告别 synchronized
java·并发工具
有位神秘人35 分钟前
Android中Notification的使用详解
android·java·javascript
tb_first2 小时前
LangChain4j简单入门
java·spring boot·langchain4j
独自破碎E2 小时前
【BISHI9】田忌赛马
android·java·开发语言
范纹杉想快点毕业2 小时前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
smileNicky2 小时前
布隆过滤器怎么提高误差率
java
それども2 小时前
分库分表的事务问题 - 怎么实现事务
java·数据库·mysql
Java面试题总结3 小时前
基于 Java 的 PDF 文本水印实现方案(iText7 示例)
java·python·pdf