MST
星途 面试题库

面试题:Java设计模式的性能优化与权衡

在高并发场景下,某些设计模式可能会带来性能瓶颈。以单例模式为例,讨论其在高并发环境下不同实现方式的性能特点及线程安全问题,以及如何权衡使用不同的单例实现来满足性能需求。同时说明在何种情况下,需要对设计模式进行性能优化改造,改造思路是什么?
23.5万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

单例模式在高并发环境下不同实现方式的性能特点及线程安全问题

  1. 饿汉式单例
    • 实现方式:在类加载时就创建单例实例。
    public class EagerSingleton {
        private static final EagerSingleton instance = new EagerSingleton();
        private EagerSingleton() {}
        public static EagerSingleton getInstance() {
            return instance;
        }
    }
    
    • 性能特点:由于在类加载时就创建实例,不存在并发创建的性能开销,调用 getInstance 方法时直接返回实例,速度快。
    • 线程安全:线程安全,因为类加载机制保证了实例只会被创建一次。
  2. 懒汉式单例(非线程安全)
    • 实现方式:在第一次调用 getInstance 方法时才创建实例。
    public class LazySingleton {
        private static LazySingleton instance;
        private LazySingleton() {}
        public static LazySingleton getInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }
    
    • 性能特点:延迟初始化,在实例未创建时,调用 getInstance 方法性能较好。但当多线程并发调用 getInstance 且实例未创建时,可能会创建多个实例。
    • 线程安全:非线程安全,多线程环境下可能出现多个实例。
  3. 懒汉式单例(线程安全,同步方法)
    • 实现方式:通过在 getInstance 方法上加 synchronized 关键字保证线程安全。
    public class LazySafeSingleton {
        private static LazySafeSingleton instance;
        private LazySafeSingleton() {}
        public static synchronized LazySafeSingleton getInstance() {
            if (instance == null) {
                instance = new LazySafeSingleton();
            }
            return instance;
        }
    }
    
    • 性能特点:由于 synchronized 关键字,每次调用 getInstance 方法都会进行同步操作,性能开销较大,尤其是在高并发场景下。
    • 线程安全:线程安全,保证了只有一个实例被创建。
  4. 双重检查锁定(DCL)单例
    • 实现方式:通过两次检查 instance 是否为 null 并使用 synchronized 块来保证线程安全和性能。
    public class DoubleCheckLockSingleton {
        private volatile static DoubleCheckLockSingleton instance;
        private DoubleCheckLockSingleton() {}
        public static DoubleCheckLockSingleton getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckLockSingleton.class) {
                    if (instance == null) {
                        instance = new DoubleCheckLockSingleton();
                    }
                }
            }
            return instance;
        }
    }
    
    • 性能特点:第一次 if (instance == null) 检查避免了不必要的同步操作,只有在实例未创建时才进入同步块,性能相对较好。volatile 关键字保证了实例的可见性和禁止指令重排序。
    • 线程安全:线程安全,通过双重检查和 volatile 关键字确保了在多线程环境下只有一个实例被创建。
  5. 静态内部类单例
    • 实现方式:利用静态内部类的特性实现延迟初始化。
    public class StaticInnerClassSingleton {
        private StaticInnerClassSingleton() {}
        private static class SingletonHolder {
            private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
        }
        public static StaticInnerClassSingleton getInstance() {
            return SingletonHolder.instance;
        }
    }
    
    • 性能特点:延迟初始化,在调用 getInstance 方法时才加载静态内部类并创建实例。同时,由于类加载机制保证了线程安全,性能较好。
    • 线程安全:线程安全,借助类加载机制保证了实例只会被创建一次。
  6. 枚举单例
    • 实现方式:使用枚举类型实现单例。
    public enum EnumSingleton {
        INSTANCE;
        // 可以添加其他方法和属性
    }
    
    • 性能特点:实现简单,线程安全,且在反序列化时也能保证单例。由于枚举类型在类加载时就被初始化,调用 INSTANCE 性能较好。
    • 线程安全:线程安全,枚举类型本身保证了实例的唯一性。

如何权衡使用不同的单例实现来满足性能需求

  1. 性能要求不高且线程安全要求低:可以使用懒汉式单例(非线程安全),适用于单线程环境或对线程安全要求不严格的场景。
  2. 对线程安全要求高但性能要求不极致:懒汉式单例(同步方法)可以满足需求,但在高并发场景下性能较差。
  3. 高并发且对性能要求较高:双重检查锁定(DCL)单例、静态内部类单例或枚举单例是较好的选择。DCL 单例通过双重检查和 volatile 关键字在保证线程安全的同时提升性能;静态内部类单例利用类加载机制实现延迟初始化和线程安全;枚举单例实现简单且线程安全,还能防止反序列化破坏单例。

在何种情况下需要对设计模式进行性能优化改造及改造思路

  1. 情况:当设计模式在高并发场景下出现性能瓶颈,如频繁的同步操作导致大量线程等待,或者实例创建开销过大影响系统响应速度时,需要进行性能优化改造。
  2. 改造思路
    • 减少同步范围:例如从方法级同步改为块级同步,像 DCL 单例那样,只在必要时进行同步操作,减少线程等待时间。
    • 延迟初始化:采用静态内部类单例或懒汉式单例的优化方式,避免在系统启动时就创建大量不必要的实例,减少资源消耗。
    • 缓存复用:对于一些创建开销大的对象,可以在单例中缓存已创建的对象并复用,减少创建次数。
    • 使用更高效的数据结构或算法:根据具体业务场景,优化单例内部的数据结构和算法,提高操作效率。