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() {}
}
相关推荐
养军博客1 小时前
Spring boot 简单开发接口
java·spring boot·后端
喜欢便码1 小时前
xml与注解的区别
xml·java·开发语言
Rubypyrrha2 小时前
Spring MVC常见注解详解
java·spring·mvc
钢铁男儿2 小时前
Python中的标识、相等性与别名:深入理解对象引用机制
java·网络·python
AllenO.o2 小时前
Redis五种数据结构详解
java·数据结构·数据库·redis·缓存
重生之后端学习2 小时前
day23-集合(泛型&Set&数据结构)
java·开发语言·数据结构·算法
码农飞哥2 小时前
互联网大厂Java面试实战:从Spring Boot到微服务的技术问答与解析
java·数据库·spring boot·安全·微服务·面试·电商
雨落白笙2 小时前
端口转发与跨域处理
java
曼岛_3 小时前
[Java实战]Spring Boot 定时任务(十五)
java·spring boot·python
oliveira-time3 小时前
app加固
java