java 设计模式之单例模式

发布于:2025-04-18 ⋅ 阅读:(16) ⋅ 点赞:(0)

简介

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

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

单例模式的实现方式

饿汉式

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

案例:

public class Singleton {
    private static Singleton instance = new Singleton();
    
    private Singleton() { }
    
    public static Singleton getInstance() {
        return instance;
    }
}

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

懒汉式加双重检查加锁

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

案例:

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行伪代码:

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

上面的代码在编译器运行时,可能出现重排序,从 1-2-3 变为 1-3-2,在多线程环境下就会出现问题,使用 volatile 关键字会禁止这种重排序。

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

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

案例:

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程序的运行环境

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() {}
}

网站公告

今日签到

点亮在社区的每一天
去签到