【常见单例模式】

本文介绍了单例模式的设计原则和重要性,包括其构造函数为私有、静态变量存储唯一实例以及公开静态方法获取实例的特点。讨论了单例模式在多线程环境中的线程安全问题,并详细阐述了饿汉式、懒汉式、Double Check + Volatile、内部类以及枚举类五种实现方式,分析了各自的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常见单例模式

单例模式Singleton Design Pattern)保证一个类只能有一个实例。

单例模式的实现需要三个必要的条件

  1. 单例类的构造函数必须是私有的,这样才能将类的创建权控制在类的内部,从而使得类的外部不能创建类的实例。
  2. 单例类通过一个私有的静态变量来存储其唯一实例。
  3. 单例类通过提供一个公开的静态方法,使得外部使用者可以访问类的唯一实例。

注意:
因为单例类的构造函数是私有的,所以单例类不能被继承。

另外,实现单例类时,还需要考虑三个问题:

  • 创建单例对象时,是否线程安全
  • 单例对象的创建,是否延时加载
  • 获取单例对象时,是否需要加锁

下面介绍五种实现单例模式的方式。

饿汉式
  • 优点:

    • 线程安全。
    • 获取对象,不需要加锁。
  • 缺点:

    • 不支持延时加载。
  • 代码:

    /**
     *饿汉式
     */
    public class SingleDemoOne {
        private SingleDemoOne(){}
    
        private static final SingleDemoOne instance = new SingleDemoOne();
    
        public static SingleDemoOne getInstance(){
            return instance;
        }
    
    }
    
    
懒汉式
  • 优点:

    • 线程安全。
    • 支持延时加载。
  • 缺点:

    • 获取对象,需要加锁。
  • 代码:

    /**
     * lazy init
     */
    public class SingleDemoTwo {
    
        private SingleDemoTwo() {};
    
        private static SingleDemoTwo instance;
    
        public static synchronized SingleDemoTwo getInstance(){
            if(instance == null)
                instance = new SingleDemoTwo();
            return instance;
        }
    
    }
    
    
Double Check + Volatile

问题1:为什么需要 double check ?

答:考虑以下情况,线程 A 和 B 同时阻塞在了 synchronized (SingleDemoThree.class) ,并且线程 A 获得了锁,线程 A 创建了一个实例,并将其赋值给 instance ,然后释放锁,并且拿这在这个引用在线程 A 中继续工作;此时 B 获得了锁,并且重新创建了一个实例(因为进入同步块之后,它没有判断 instance是否为 null ),然后将引用赋值给 instance ,此时线程 A 引用的 instance 的实例已经被修改,但是它并不知情。

double check 可以解决上面这个问题。

问题2:为什么需要 volatile ?

答:JVM 中并没有规定实例化对象和赋值该实例的引用的先后顺序,也就是说 instance 不为 null 时,可能引用的对象实际上并没有创建完全,可能只是刚刚分配了内存(在创建的过程中)。此时其他线程拿到的对象可能是不完整的。

使用 volatile 关键字修饰 instance 之后,可以保证 instance 的写优先,就是说,如果其他线程在获取 instance 时,如果发现它正在被创建,会等到它创建完全之后,再返回它的引用。

  • 优点:

    • 线程安全。
    • 支持延时加载。
    • 获取时不需要加锁。
  • 代码:

    /**
     * double check + volatile
     */
    public class SingleDemoThree {
    
        private SingleDemoThree() {}
    
        private volatile static SingleDemoThree instance;
    
        public static SingleDemoThree getInstance(){
            if(instance == null){
                synchronized (SingleDemoThree.class){
                    if(instance == null){
                        instance = new SingleDemoThree();
                    }
                }
            }
            return instance;
        }
    }
    
内部类

InnerHolder 是内部类,外部类 SingleDemoFour 被加载的是,并不会加载内部类。只有当调用 getInstance() 方法,主动去访问内部类静态成员变量,此时 InnerHolder 才会被加载,instance 才会被实例化。

  • 优点:

    • 线程安全。
    • 支持延时加载。
    • 获取时不需要加锁。
  • 代码:

    /**
     * inner class
     */
    public class SingleDemoFour {
        private SingleDemoFour(){}
    
        public void fun(){
            System.out.println("hello");
        }
    
        private static class InnerHolder{
            private final static SingleDemoFour instance = new SingleDemoFour();
        }
    
        public static SingleDemoFour getInstance(){
            return InnerHolder.instance;
        }
    
    }
    
枚举类

枚举类型不允许被继承;同时只能被实例化一次;但是不能懒加载。

  • 优点:

    • 线程安全。
    • 获取时不需要加锁。
  • 缺点:

    • 不支持延时加载。
  • 代码:

    /**
     * enum
     */
    public enum SingleDemoFive {
        INSTANCE;
        SingleDemoFive(){}
    
        public void fun(){
            System.out.println("ok");
        }
    
        public static  SingleDemoFive getInstance(){
            return INSTANCE;
        }
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值