Java 实战:无锁方式实现高性能线程安全单例

Java线程安全单例的4种实现与volatile原理 单例模式是面向对象编程中基础但关键的设计模式,其核心在于保证全局唯一实例。在多线程环境下,该目标面临原子性与可见性的双重挑战——竞态条件可能导致多个实例被创建,而内存可见性缺失则可能使线程读取到未完全初始化的对象。Java通过synchronized提供互斥与可见性保障,但存在性能瓶颈;volatile则通过禁止指令重排序和插入内存屏障,精准解决对象构造过程中的发布问题。结合DCL(双重检查锁定)、静态内部类、枚举及饿汉式等主流方案,开发者需根据启动开销、延迟加载需求与JVM机制选择最优解。本文深入剖析vo 阅读详情

每个 Java 开发者都曾被多线程环境下的单例问题困扰过。明明只想创建一个实例,却因为并发操作导致对象被重复创建,debug 半天才发现问题。传统的加锁方案虽然安全,但会带来不小的性能开销,那么有没有既安全又高效的方案呢?

单例模式的线程安全挑战

单例模式在系统中确保一个类只有唯一实例,并提供全局访问点。然而在多线程环境下,常见的懒汉式实现会遇到线程安全问题:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            // 多线程环境下的危险区域
            instance = new Singleton();
        }
        return instance;
    }
}

上面的代码在多线程访问时,可能出现多个线程同时判断instance == null为真,导致创建多个实例。

最常见的解决方案是使用synchronized修饰获取实例的方法:

public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

但这种方式有明显缺点:每次调用都要获取锁,即使单例已经初始化完成,仍然要经过锁的开销,严重影响性能。

接下来,我将介绍几种无锁或几乎无锁的线程安全单例实现方式。

静态内部类(Holder 模式)实现单例

静态内部类方式可能是最优雅的懒加载单例实现方式,它利用了 Java 类加载机制的线程安全特性:

public class LazyInitSingleton implements Serializable {
    private static final long serialVersionUID = 1234567L;

    // 私有构造函数,防御反射攻击
    private LazyInitSingleton() {
        if (SingletonHolder.INSTANCE != null) {
            throw new IllegalStateException("单例已经初始化,不允许创建多个实例");
        }
    }

    // 可以添加带参数的构造方法
    private LazyInitSingleton(Config config) {
        // 防御性检查避免NPE
        if (config == null) {
            throw new IllegalArgumentException("Config不能为null");
        }
        this.someConfig = config.getValue();
    }

    private String someConfig;

    // 静态内部类,只有在使用时才会被加载
    private static class SingletonHolder {
        // 初始化时可以传入参数
        private static final LazyInitSingleton INSTANCE = new LazyInitSingleton(Config.getDefaultConfig());
    }

    public static LazyInitSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 解决序列化问题的关键方法
    private Object readResolve() {
        // 返回单例实例,避免反序列化创建新对象
        return SingletonHolder.INSTANCE;
    }
}

这种实现的原理可以用下面的流程图解释:

工作原理

  • 外部类 LazyInitSingleton 加载时,内部类 SingletonHolder 不会立即被加载
  • 只有首次调用 getInstance()方法时,JVM 才会加载 SingletonHolder 类
  • 类的加载和初始化过程是线程安全的,由 JVM 保证
  • 静态变量 INSTANCE 只会初始化一次

举个简单例子:想象一下,你有一个精致的手表,但不想随身携带。你把它放在家里的保险柜中,只有当你需要查看时间时才会打开保险柜拿出手表。这就像静态内部类实现的单例——实例(手表)只有在第一次需要时才会被创建(从保险柜取出)。

序列化安全问题

静态内部类方式默认不是序列化安全的。如果单例类实现了 Serializable 接口,反序列化时会创建新实例,破坏单例性。解决方法是:

  1. 必须实现 Serializable 接口(如示例中所示)
  2. 添加 readResolve()方法,确保反序列化时返回单例实例
  3. 定义 serialVersionUID 避免兼容性问题

这种方式实现了真正的延迟加载(类加载时机由 JVM 控制),保证了线程安全,而且没有使用锁,性能非常好。

枚举实现单例

枚举可能是实现单例最简单的方式,由 Java 语言规范保证枚举实例的唯一性:

// 枚举单例可以实现接口
public enum EnumSingleton implements Initializable, AutoCloseable {
    INSTANCE;

    // 可以添加属性和方法
    private String data;
    private Connection dbConnection;

    // 枚举构造函数会在类加载时执行一次
    EnumSingleton() {
        // 初始化资源
        System.out.println("枚举单例初始化");
        // 实际项目中可能的数据库连接初始化示例:
        // try {
        //     dbConnection = DriverManager.getConnection(
        //         "jdbc:mysql://localhost:3306/mydb", "user", "password");
        // } catch (SQLException e) {
        //     throw new RuntimeException("数据库连接失败", e);
        // }
    }

    // 实现接口的初始化方法
    @Override
    public void initialize() {
        System.out.println("单例初始化完成");
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    // 业务方法
    public void doSomething() {
        System.out.println("单例正在执行业务逻辑");
    }

    // 实现AutoCloseable接口,优雅关闭资源
    @Override
    public void close() {
        // 关闭资源
        if (dbConnection != null) {
            try {
                dbConnection.close();
            } catch (Exception e) {
                // 处理异常
            }
        }
    }
}

使用方式非常简单:

// 获取实例
EnumSingleton singleton = EnumSingleton.INSTANCE;
// 调用方法
singleton.doSomething();
// 初始化
singleton.initialize();
// 使用try-with-resources自动关闭资源
try (EnumSingleton singleton = EnumSingleton.INSTANCE) {
    singleton.doSomething();
}

枚举实现单例的优势:

  • 代码简洁,实现最少
  • 线程安全由 JVM 保证
  • 自动防止反序列化创建新实例
  • 防止反射攻击(JVM 禁止反射创建枚举实例)
  • 可以实现接口,具有良好的扩展性

《Effective Java》第三版中,Joshua Bloch 明确指出:"单元素枚举类型是实现单例的最佳方式",因为它不仅能解决线程安全问题,还能防止反序列化和反射攻击。

注意:枚举单例在类加载时就会初始化(与饿汉式类似),不提供延迟加载能力。如果初始化成本高,且不一定会使用单例,可能会造成资源浪费。这与静态内部类最大的区别是初始化时机。

就像你家里的应急灯一样,它在你家里通电的那一刻就已经充电待命了,不管你是否真的会遇到停电情况。枚举单例也是如此,它在程序启动时就已经准备好了,不管你是否会用到它。

使用 CAS 操作实现单例

Compare-And-Swap(比较并交换)是一种无锁算法,可以通过 CPU 的原子指令来实现线程安全的单例:

import java.util.concurrent.atomic.AtomicReference;

public class CASSingleton {
    // AtomicReference内部使用硬件级别的原子指令实现CAS
    private static final AtomicReference<CASSingleton> INSTANCE =
        new AtomicReference<>();

    private CASSingleton() {}

    // 优化版本:减少实例创建次数
    public static CASSingleton getInstance() {
        // 首先检查是否已有实例
        CASSingleton current = INSTANCE.get();
        if (current != null) {
            return current;
        }

        // 只有实例不存在时才创建新实例
        CASSingleton newInstance = new CASSingleton();
        if (INSTANCE.compareAndSet(null, newInstance)) {
            // CAS成功,返回新创建的实例
            return newInstance;
        } else {
            // CAS失败,说明其他线程已经设置了实例
            // 丢弃我们创建的实例
            return INSTANCE.get();
        }
    }
}

CAS 操作的原理流程:

想象一下,这就像两个人同时尝试在空白纸上写下自己的名字。只有第一个人能成功在纸上写下名字(CAS 成功),第二个人看到纸上已经有名字了(CAS 失败),就会放弃写自己的名字,而是记住纸上写的那个名字。

这种方式有几个特点:

  • 完全无锁,使用 CPU 硬件级别的原子指令保证线程安全
  • 实现了延迟加载
  • 优化版本减少了不必要的对象创建
  • 在极高并发场景下性能优于细粒度锁方案

CAS 底层原理

CAS 操作依赖处理器的原子指令,如 x86 架构的CMPXCHG指令,能够在单个 CPU 周期内完成"比较并交换"操作。不同于锁的阻塞机制,CAS 是非阻塞的,线程可以立即得到结果并决定下一步操作。

然而,高并发情况下多线程同时执行 CAS 可能导致"总线风暴",因为每个 CPU 核心都会争夺内存总线访问权,影响整体性能。Java 的AtomicReference通过 Unsafe 类映射到这些硬件指令。

要验证 CAS 的效果,可以通过 JVM 参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly查看实际的汇编代码,其中会看到cmpxchg等指令的使用。

性能测试数据

在一项针对 100 万次调用的测试中,CAS 方式在高并发环境下的性能表现:

虽然 CAS 在线程数增加时性能有所下降,但仍明显优于同步锁方式。

双重检查锁+volatile 的优化

双重检查锁使用细粒度锁方案,虽然不是完全无锁,但它将锁的使用范围降到最小:

public class DCLSingleton {
    // volatile关键字确保了:
    // 1. 可见性:所有线程看到的instance值是最新的
    // 2. 禁止指令重排序(禁止JVM重排序):防止构造函数还未执行完就返回实例
    private static volatile DCLSingleton instance;

    private DCLSingleton() {}

    public static DCLSingleton getInstance() {
        // 第一次检查:无锁检查,大多数情况下可以避免加锁
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                // 第二次检查:防止多个线程同时通过第一次检查
                if (instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

就像排队买票,第一次检查相当于远远看一眼售票窗口是否有人,如果已经有人在那里工作(实例已创建),你就不用排队了(避免加锁)。只有当你看到没人(实例为 null)时,才会去排队(获取锁),排到你时再确认一下确实没人(再次检查实例是否为 null),才会创建一个新的售票员(创建实例)。

这种实现的关键是volatile关键字,它确保了:

  1. 可见性:一个线程的修改对其他线程立即可见
  2. 禁止指令重排序:防止实例创建过程中的重排序问题

指令重排序问题解析

instance = new DCLSingleton();这行代码实际上包含三个操作:

  1. 分配内存空间
  2. 调用构造函数初始化对象
  3. 将引用指向分配的内存

在没有 volatile 修饰的情况下,JVM 可能会优化指令顺序,导致步骤 3 在步骤 2 之前执行:

这就像给别人一个房子钥匙,但房子内部装修还没完成一样。如果没有 volatile 禁止这种重排序,其他线程可能会拿到钥匙(引用非空)进入一个还没装修好的房子(未初始化的对象)。

从 Java 5 开始,volatile 关键字提供了"happens-before"保证,确保对 volatile 变量的写操作会在读操作之前完成,从而防止上述问题。

双重检查的执行流程:

这种方式只有在首次创建实例时才使用锁,后续访问不再获取锁,因此性能损失很小。

防御反射和序列化攻击

即使实现了线程安全的单例,仍然可能面临反射和序列化的攻击:

反射攻击示例

// 攻击代码:使用反射破坏单例
try {
    Constructor<LazyInitSingleton> constructor =
        LazyInitSingleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    LazyInitSingleton instance1 = LazyInitSingleton.getInstance();
    LazyInitSingleton instance2 = constructor.newInstance();
    System.out.println(instance1 == instance2); // 输出false,单例被破坏
} catch (Exception e) {
    e.printStackTrace();
}

就像有人知道了保险柜的密码(反射),即使你设置了访问限制(私有构造函数),他们仍然可以打开它并拿出手表的复制品。

序列化攻击示例

// 攻击代码:使用序列化破坏单例
try {
    LazyInitSingleton instance1 = LazyInitSingleton.getInstance();

    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(instance1);
    oos.close();

    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    LazyInitSingleton instance2 = (LazyInitSingleton) ois.readObject();
    ois.close();

    System.out.println(instance1 == instance2); // 如果没有readResolve方法,输出false
} catch (Exception e) {
    e.printStackTrace();
}

这就像复印了保险柜和手表的完整信息(序列化),然后根据这些信息在另一个地方重建了一个相同的保险柜和手表(反序列化)。

防御方法:

  1. 枚举单例:天然防御反射和序列化攻击
  2. 静态内部类:必须添加readResolve()方法防御序列化攻击,构造函数可添加检查防御反射攻击
  3. 构造函数检查:在私有构造函数中添加判断,防止反射创建多个实例

实际应用场景分析

根据不同的业务场景,我们可以选择最合适的单例实现方式:

场景 1:配置管理器

当你需要一个全局配置管理器,而且配置数据只需要加载一次时:

// 使用静态内部类实现的配置管理器
public class ConfigManager implements Serializable {
    private static final long serialVersionUID = 7654321L;
    private Map<String, String> configs;

    private ConfigManager() {
        // 初始化配置
        configs = new HashMap<>();
        configs.put("dbUrl", "jdbc:mysql://localhost:3306/mydb");
        configs.put("maxConnections", "100");
    }

    // 允许通过配置文件初始化
    private ConfigManager(String configPath) {
        configs = new HashMap<>();
        // 从指定路径加载配置
        loadFromFile(configPath);
    }

    private void loadFromFile(String path) {
        // 实际加载配置的代码
    }

    private static class Holder {
        // 使用默认配置初始化
        private static final ConfigManager INSTANCE = new ConfigManager();
    }

    public static ConfigManager getInstance() {
        return Holder.INSTANCE;
    }

    public String getConfig(String key) {
        return configs.get(key);
    }

    // 防止反序列化创建新实例
    private Object readResolve() {
        return Holder.INSTANCE;
    }
}

场景 2:线程池管理器

当你需要一个全局的线程池管理器,且希望防止反序列化和反射攻击时:

// 使用枚举实现的线程池管理器
public enum ThreadPoolManager {
    INSTANCE;

    private final ExecutorService executorService;
    private final ScheduledExecutorService scheduledExecutorService;

    ThreadPoolManager() {
        // 根据CPU核心数创建固定大小线程池
        int processors = Runtime.getRuntime().availableProcessors();
        executorService = new ThreadPoolExecutor(
            processors,                    // 核心线程数,保持活跃的线程数
            processors * 2,                // 最大线程数,允许的最大线程数量
            60L, TimeUnit.SECONDS,         // 空闲线程存活时间
            new LinkedBlockingQueue<>(),   // 工作队列,存储等待执行的任务
            new ThreadFactory() {          // 自定义线程工厂
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    t.setName("worker-" + t.getId());
                    t.setDaemon(true);     // 设置为守护线程
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者线程执行
        );

        // 创建调度线程池
        scheduledExecutorService = Executors.newScheduledThreadPool(2);
    }

    public void execute(Runnable task) {
        executorService.execute(task);
    }

    public Future<?> submit(Callable<?> task) {
        return executorService.submit(task);
    }

    public ScheduledFuture<?> scheduleTask(Runnable task, long delay, TimeUnit unit) {
        return scheduledExecutorService.schedule(task, delay, unit);
    }

    public void shutdown() {
        executorService.shutdown();
        scheduledExecutorService.shutdown();
    }
}

场景 3:数据库连接管理

对于需要延迟初始化且初始化过程较重的场景:

// 使用双重检查实现的数据库连接管理器
public class DBConnectionManager implements AutoCloseable {
    private static volatile DBConnectionManager instance;
    private DataSource dataSource;

    private DBConnectionManager() {
        try {
            // 初始化数据源
            initDataSource();
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize data source", e);
        }
    }

    private void initDataSource() {
        // 使用连接池而不是单一连接
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("user");
        config.setPassword("password");
        config.setMaximumPoolSize(10);  // 最大连接数
        config.setMinimumIdle(5);       // 最小空闲连接

        dataSource = new HikariDataSource(config);
    }

    public static DBConnectionManager getInstance() {
        if (instance == null) {
            synchronized (DBConnectionManager.class) {
                if (instance == null) {
                    instance = new DBConnectionManager();
                }
            }
        }
        return instance;
    }

    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    @Override
    public void close() {
        if (dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
    }
}

// 依赖项(Maven):
// <dependency>
//     <groupId>com.zaxxer</groupId>
//     <artifactId>HikariCP</artifactId>
//     <version>4.0.3</version>
// </dependency>

场景 4:分布式系统中的单例

在分布式环境中,单例只能保证单个 JVM 内的唯一性,要实现全局唯一需要额外机制:

// 使用Redis分布式锁实现的分布式单例
public class DistributedSingleton {
    private static volatile DistributedSingleton instance;
    private static RedissonClient redissonClient;
    private static final int MAX_RETRIES = 3;

    static {
        // 初始化Redisson客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        redissonClient = Redisson.create(config);
    }

    private DistributedSingleton() {}

    public static DistributedSingleton getInstance() {
        if (instance == null) {
            // 获取分布式锁
            RLock lock = redissonClient.getLock("singleton_lock");
            boolean lockAcquired = false;
            try {
                // 尝试获取锁,最多等待5秒,锁有效期10秒
                lockAcquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
                if (lockAcquired) {
                    if (instance == null) {
                        instance = new DistributedSingleton();
                    }
                } else {
                    // 获取锁失败,可能是高并发或网络问题
                    // 重试或返回已有实例
                    if (instance != null) {
                        return instance;
                    }
                    // 重试策略
                    return retryGetInstance(MAX_RETRIES);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                if (lockAcquired) {
                    lock.unlock();
                }
            }
        }
        return instance;
    }

    private static DistributedSingleton retryGetInstance(int retries) {
        if (retries <= 0) {
            throw new RuntimeException("获取分布式单例实例超过最大重试次数");
        }

        try {
            Thread.sleep(100);
            return getInstance();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return retryGetInstance(retries - 1);
        }
    }
}

// 依赖项(Maven):
// <dependency>
//     <groupId>org.redisson</groupId>
//     <artifactId>redisson</artifactId>
//     <version>3.17.0</version>
// </dependency>

分布式单例就像是多个城市中的唯一银行金库,每个城市(JVM)内部可以有本地的管理者(本地单例),但要确保全球(分布式系统)只有一个主金库,就需要一个中央协调机构(Redis)来控制。

单例模式与依赖注入

在现代应用架构中,单例模式与依赖注入(DI)框架有一定的关系:

// Spring框架中的单例Bean
@Component
public class SpringSingleton {
    // Spring默认为单例作用域
    private String data;

    // 通过构造函数注入依赖
    @Autowired
    public SpringSingleton(DataService dataService) {
        this.data = dataService.loadData();
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

// 使用方式
@Service
public class UserService {
    private final SpringSingleton singleton;

    @Autowired
    public UserService(SpringSingleton singleton) {
        this.singleton = singleton;
    }
}

使用 DI 框架的优势:

  • 生命周期管理更灵活(可配置作用域)
  • 便于测试(可以注入模拟对象)
  • 更容易实现面向接口编程
  • 自动管理依赖关系

Spring 容器负责确保单例的唯一性,同时提供了额外功能如懒加载(@Lazy注解)和作用域控制。

单例模式的单元测试

测试单例类时,一个常见问题是如何隔离测试环境:

// 使用可测试的单例模式
public class TestableSingleton {
    private static TestableSingleton instance;

    // 用于测试的重置方法
    static void reset() {
        instance = null;
    }

    private TestableSingleton() {}

    public static TestableSingleton getInstance() {
        if (instance == null) {
            synchronized (TestableSingleton.class) {
                if (instance == null) {
                    instance = new TestableSingleton();
                }
            }
        }
        return instance;
    }
}

// JUnit测试代码
public class TestableSingletonTest {
    @Before
    public void setup() {
        TestableSingleton.reset();
    }

    @Test
    public void testSingletonInstance() {
        TestableSingleton instance1 = TestableSingleton.getInstance();
        TestableSingleton instance2 = TestableSingleton.getInstance();
        assertSame("单例实例应该相同", instance1, instance2);
    }

    @Test
    public void testMultithreadedAccess() throws Exception {
        // 多线程测试
        final int threadCount = 10;
        final CountDownLatch latch = new CountDownLatch(threadCount);
        final Set<TestableSingleton> instances = Collections.synchronizedSet(new HashSet<>());

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                instances.add(TestableSingleton.getInstance());
                latch.countDown();
            }).start();
        }

        latch.await(1, TimeUnit.SECONDS);
        assertEquals("多线程下应该只有一个单例实例", 1, instances.size());
    }
}

各实现方式性能对比

以下是不同实现方式在各种并发级别下的性能测试结果(每秒处理请求数):

从数据可以看出,枚举和静态内部类在各种并发级别下性能最好,且随着并发增加性能衰减较少。CAS 方法在高并发下性能下降较明显,但仍优于使用锁的方法。这就像不同道路在交通流量增加时的通行效率一样。

如何选择最佳实现方式

下面的决策流程图可以帮助你选择合适的单例实现方式:

总结

实现方式

线程安全

延迟加载

防反射攻击

防反序列化

性能(百万次/秒)

实现复杂度

JDK 要求

适用场景

静态内部类

✓

✓

✗(可添加)

✗(需添加方法)

30-37

低

1.2+

大多数场景,尤其是需要延迟加载的情况

枚举方式

✓

✗

✓

✓

32-38

最低

1.5+

需要防止反射和序列化攻击的场景

CAS 方式

✓

✓

✗

✗

22-35

中

1.5+

极高并发且对锁敏感的场景

双重检查锁

✓

✓

✗

✗

20-34

中

1.5+

需要延迟加载且初始化过程复杂的场景

分布式实现

✓

视情况

✗

✗

<10

高

视情况

分布式系统中需要全局唯一的场景

无锁实现单例的方式各有优劣,选择时需要考虑以下因素:

  • 是否需要延迟加载
  • 初始化成本有多高
  • 是否需要防止反射和序列化攻击
  • 系统的并发级别
  • 是否为分布式环境

在大多数场景下,静态内部类和枚举方式是最佳选择。在选择时应根据业务场景的具体需求进行权衡,优先考虑代码简洁性和可维护性。

真正的高手不仅知道各种实现方式,更懂得在什么场景下选择最合适的方案。就像一个好厨师不仅了解各种食材,更知道什么菜该用什么食材一样。

Java ConcurrentHashMap:线程安全的哈希表实现 是Java集合框架中一颗璀璨的明珠,它通过巧妙的设计和高效的实现提供了无与伦比的并发性能。无论是早期的分段锁机制还是现代的CAS操作和树化技术,都展示了Java开发者对于并发编程的深刻理解和精湛技艺。在内部,它将整个哈希表分为多个段(Segment),每个段都是一个独立的锁,不同的线程可以持有不同段的锁,从而实现真正的并发访问。同时,它也采用了CAS(Compare-and-Swap)操作来减少锁的使用,从而提供了更高的吞吐量。CAS是一种无锁的技术,它可以在不阻塞线程的情况下更新共享变量的值。 阅读详情

相关推荐

MS51内部振荡器

MS51内部有两个RC振荡器,一个高速16MHz或24MHz(HIRC),可通过HIRC24(RCTRIM1.4)位进 行选择配置,以及一个低速10 kHz(LIRC)。它们都可被选择用作系统时钟。通过设置HIRCEN (CKEN.5)位使能HIRC,设备上电时LIRC被使能。用户可设置OSC[1:0] (CKSWT [2:1])为[0,0]选择HIRC 作为系统时钟,设置OSC[1:0](CKSWT [1:0])为[1,0]选择LIRC作为系统时钟。注意:MS51上电后HIRC 和 LIRC都被...

seraphseraphhill2008的博客 808

写一个线程安全的单例模式

写一个线程安全的单例模式

pipizhen_的博客 2338

【电动汽车再生制动控制技术(万字长文,图文并茂)】

电动汽车再生制动的基本原理是:通过具有可逆作用的电动机/发电机来实现电动汽车动能和电能的转化。在汽车减速或制动时,可逆电机以发电机形式工作,汽车行驶的动能带动发电机将汽车动能转化为电能并储存在储能器(蓄电池或超级电容器)中;在汽车起动或加速时,可逆电机以电动机形式工作,将储存在储能器中的电能转化为机械能给汽车。这对于改善汽车的能量利用效率,延长电动汽车的行驶距离是至关重要的。

优快云-新能源汽车研发测试专业博主 1578

Java单例模式(四种线程安全的)

代码】Java单例模式(四种线程安全的)

Weeeex的博客 981

100个Python实战项目(七)实现摩斯密码翻译器

作者主页:海拥 作者简介:优快云全栈领域优质创作者、HDZ核心组成员、蝉联C站周榜前十 粉丝福利:往期获奖记录 每周送6~9本书,不定期送各种小礼品(搪瓷杯、抱枕、鼠标垫、马克杯等) 摩斯密码是一种将文本信息作为一系列通断的音调、灯光或咔嗒声传输的方法,无需特殊设备,熟记的小伙伴即可直接翻译。它以电报发明者Samuel F. B. Morse的名字命名。 算法 算法非常简单。英语中的每个字符都被一系列“点”和“破折号”代替,或者有时只是单数的“点”或“

努力是为了站在万人之中,成为别人的光 1万+

【深度学习】什么是自回归模型?

自回归模型是一种重要的时间序列模型,通过利用过去的值来预测当前或未来的值。它在统计学、经济学、自然语言处理等领域有广泛的应用。自回归模型的简单性和可解释性使其成为许多应用的首选,但在更复杂的情况下,可能需要结合其他模型如移动平均模型(MA模型)或混合模型(如ARIMA)来更好地捕捉时间序列中的模式。扩散模型(Diffusion Model)和自回归模型(Autoregressive Model)是两种不同的生成模型,各自有独特的特点和应用场景。

q742971636的博客 1671

Java多线程基础-8:单例模式及其线程安全问题

单例指的就是单个实例(instance),也就是单个对象(对象就是类的实例)。单例模式指的是某个类在进程中只有唯一一个实例(在一个程序中,只能创建一个实例(一个对象),不能创建多个对象)。按理来说,在写代码的时候多 new 几次,就能创建多个对象了。但在语法上,是有办法禁止这样多 new 几次的操作的。也就是说,Java中的单例模式,实际上是借助 Java 语法,保证某个类只能够创建出一个实例,而不能被new多次。为什么会有这样的用途?其实原因是很简单的:在有些场景下,本身它就要求某个概念是单例的。

wyd_333的博客 3315

杰理SDK入门教程(二):USB升级程序

开发板升级操作指南 摘要:本文介绍了开发板程序升级的两种方式(常规升级和串口升级),重点说明了常规升级的操作步骤: 拨码设置:提供4种拨码方式(不拨/1档/2档/3档),对应不同指示灯状态和按键要求 硬件连接:强制升级工具需正确连接电脑和开发板(注意USB接口方向) 升级流程:根据拨码状态操作按键,观察指示灯变化(红灯常亮绿灯灭表示进入升级模式) 执行升级:运行SDK中的download.bat完成升级 文中还包含拨码开关说明、指示灯状态对照表以及升级原理说明,帮助用户理解升级过程。升级成功后在设备管理器可

qq_42014563的博客 1120

实现一个线程安全的单例模式

上述代码中,由于t1线程执行完1 3 之后 调度走,此时instance 指向的是一个非null的 但是未初始化的对象,此时t2线程判定instance == null 不成立 就会直接return 如果t2继续使用instance里面的属性和方法,就会出现问题(此时这里的属性 都是未初始化的"全0 "值) 就会引起代码逻辑上出现问题。某个类,在一个类,只应该创建出一个实例,使用单例模式,就可以对咱们的代码进行一个更严格的校验和检查。实质是在该模式中不着急初始化出实例,在类外需要的时候,在进行初始化。

m0_70343551的博客 938

YOLO系列专栏(六):YOLO26热力图可视化 | 解析模型推理逻辑,定位检测关键区域

YOLO26作为新一代目标检测模型,其核心优势是高效的特征提取和精准的目标定位,但模型内部的推理过程的“黑盒”特性,给我们的优化带来了很大困扰:❌ 检测精度低:不知道是模型没学到关键特征,还是特征提取不充分;❌ 出现误检/漏检:无法定位问题根源——是锚框不合适?还是特征图未捕捉到目标?❌ 模型优化盲目:调参、修改网络结构全靠“猜”,缺乏针对性;❌ 难以解释模型行为:实际项目中,需要说明模型“为什么检测到这个目标”,尤其是医疗、工业等对可解释性要求高的场景。而热力图可视化。

qq_36130719的博客 732

Flutter框架GetX从入门到精通第二篇:路由管理-告别繁琐的Navigator

Flutter框架GetX从入门到精通第二篇:告别繁琐的Navigator。本系列将带你从零开始,一步步掌握 GetX 框架的核心功能与实战技巧。

鹏多多的博客 170

java实现单例模式线程安全

1111111111111111

张Sir的博客 2502

科技英语/专业英语——改错总结【2】

1.固定句式 make it + adj + 句子 例:These features make the electronic systems difficult to intercept this kind of signal. 正确形式:These features make it  difficult for the electronic systems  to intercept

MISAYAONE的博客 1732

Java单例线程安全:从创建到使用的全链路保障

单例模式是Java中最基础的设计模式,但其线程安全性远非‘加个synchronized’就能解决。本质在于:JVM仅保证类加载阶段的初始化原子性,而对象创建后的状态读写、内部集合操作、日期格式化等运行时行为,均需开发者主动构建并发安全机制。理解synchronized锁升级(偏向锁→轻量级锁→重量级锁)、volatile禁止指令重排序、final字段的happens-before语义,是实现高可靠单例的关键。真实场景中,90%的线程安全问题发生在单例使用阶段——如ConcurrentHashMap替代Has

adr5970的博客 502

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

jvm调优之懒汉式单例的实现 双重校验锁单例的特点

懒汉式单例通过双重校验锁实现线程安全的懒加载,确保在多线程环境下只创建一个单例对象,同时通过volatile关键字防止指令重排序问题。内存泄漏的常见原因包括静态集合类、监听器和回调、缓存、线程和、资源未及时关闭、内部类和匿名类的静态引用、不合理的单例实现等。通过合理管理这些资源,可以有效预防和解决内存泄漏问题。希望这篇文章对你有所帮助,如果你在开发过程中遇到内存泄漏或相关问题,欢迎在评论区留言,我们一起探讨解决方案!

赵KK的博客 859

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Java 单例模式的五种实现:饿汉式、懒汉式、DCL、静态内部类、枚举单例

在 Java 面试中,单例模式几乎是必问内容。虽然看似简单,但不同实现方式的线程安全、性能以及是否能抵御反射与序列化攻击,都存在巨大的差异。这次,卷卷就来将单例模式常见的五种实现方式进行整理和对比,适合用于学习、复习以及面试答题。饿汉式太着急,懒汉式太磨叽,DCL 看着帅但少个 volatile 就寄;懒汉式直接加锁效率太低,每次获取实例都要锁一次。今天的脑细胞库存-1,知识储备+1,算是赚了。类加载时就创建单例对象,无需加锁,线程安全。实例非常轻量,或者确定一定会使用的单例场景。实例在第一次访问时创建。

dividividiv的博客 1074

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Java 无锁方式实现高性能线程

典型的测试结果表明,在高并发场景下,基于VarHandle的实现通常比传统的DCL和枚举单例有5-10%的性能提升,而响应式单例在异步场景下表现最佳。

guygg88的博客 393

Spring单例Bean的实战应用

单例作用域的优势在于减少对象创建开销,适合无状态服务。使用时需注意避免在单例中保存可变状态,或确保线程安全。单例作用域适用于大多数Spring管理的服务类、工具类、配置类等。合理使用单例作用域能提升应用性能,但需根据业务需求权衡作用域的选择。

m0_75107803的博客 939

Java锁实现方式

首先,我们先来看下线程安全性的定义,为什么需要锁? 线程安全,即在多线程编程中,一个程序或者代码段在并发访问时,能够正确地保持其预期的行为和状态,而不会出现意外的错误或者不一致的结果。 而解决线程安全问题,主要分为两大类:1、无锁;2、有锁。 无锁的方式有: 局部变量; 对象加 final 为不可变对象; 使用 ThreadLocal 作为线程副本对象; CAS,Compare-And-Swap 即比较并交换,是 Java 十分常见的无锁实现方式。 小白:那有锁的方式呢,怎么通过加锁保证线程

WZ18810463869的博客 910

JAVA手写题-精通 Java 单例模式:三种线程安全的实现方式详解

选择哪种单例实现方式取决于具体需求:最佳选择(默认推荐):枚举(Enum)优点:最简单、最安全(线程、序列化、反射)、代码清晰。缺点:非懒加载。优秀选择(懒加载场景):静态内部类(Static Inner Class)优点:线程安全、实现懒加载、代码相对简单。缺点:可能被反射攻击。备选方案(复杂场景):双重校验锁(Double-Checked Locking)优点:线程安全、实现懒加载、理论上性能较好(避免后续同步)。

m0_73762612的博客 618

深入解析单例模式:从原理到实战,掌握Java面试高频考点

单例模式是Java设计模式中应用最广泛的创建型模式,确保类只有一个实例并提供全局访问点。本文系统讲解了常见的单例实现方式及其适用场景:饿汉式(线程安全但非延迟加载)、懒汉式(延迟加载但线程不安全)、双重检验锁(兼顾延迟加载和线程安全)。重点剖析了双重检验锁的实现原理,包括volatile关键字防止指令重排序和双重判空避免锁竞争。文章还分析了各方案的优缺点,如饿汉式实现简单但浪费内存,懒汉式线程不安全等问题。单例模式适用于系统级工具类、资源密集型服务等需要全局唯一实例的场景,是Java面试中的高频考点。

xxxxxxllllllshi的博客 987

单例的几种实现方式

单例 一、相关概念 单例:是java23中常见的设计模式之一,属于创建型模式,保证一个类只有一个实例,并对外提供调用对象该实例的方法。 意图:保证一个类在应用全局只有一个对象,减少对象的品频繁创建。 何时使用:控制实例数量,减少对象的创建,优化系统资源。 关键代码:1、构造函数私有;2、单例使用静态引用。 二、实现方式 从实例初始化的时机可以分为饿汉式、懒汉式。 饿汉式:资源加载时实例就被初始化,一般是在借用类加载机制,在类加载的初始化单例对象,可以避免多线程同步问题,但是容易产生系统垃圾。 懒汉式:第一次

weixin_43823391的博客 1558

【线程安全的单例实现方式三:synchronized的懒汉式、双重检查+volatile的懒汉式、静态内部类的懒汉式】

你现在的实现是懒加载但不是线程安全。它在多线程下会发生重复创建和“半初始化对象”问题。推荐改成静态内部类、DCL+volatile,或更稳妥地使用枚举或饿汉式(视乎是否需要延迟初始化)。

2501_93101910的博客 785

【Java并发编程核心剖析线程安全与高性能锁优化策略】

JVM自适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等技术构成Java锁的核心机制。支持可中断锁与公平锁策略,通过CAS(Compare-and-Swap)指令实现无阻塞同步。而锁粗化(Lock Coarsening)则合并循环中连续的锁竞争,避免多次加锁开销。其中,互斥锁通过“独占-独占”模式保证同一时刻仅一个线程访问临界区,但性能瓶颈明显。),通过底层硬件指令(如x86的CMPXCHG)实现无锁原子操作。锁消除(Lock Elision)通过逃逸分析,对不被多个线程访问的锁对象进行优化。

VUDEmENk的博客 914

如何手写单例

普通的单例 单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象 class Single { //1,构造一个该类的实例对象 private static Single sin = new Single(); //2,私有化构造方法 private Single(){} //3,定义一个返回函数 public static Single getIns

sekever的博客 537

笔试小结系列:写一个单例!延迟加载,线程安全,高性能

写一个单例!延迟加载,线程安全,高性能 上面提到延迟加载,所谓延迟加载,就是指当实际用到该对象时才加载对应类,否则只是声明,并未实际花费资源去初始化对象 首先先来一个最基本的,最最简单的 public class Singleton { private Singleton() {} //私有的构造函数 private static Singleton instance = null; public static Singleton getInstance() {

weixin_43337081的博客 402
上一篇: MySQL 日志系统详解:binlog、redo log、undo log 核心机制分析
下一篇: 探索 Java SPI 机制:实现灵活插件式架构的必备技术
程序员职业指南
博客等级 码龄4年 2136粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值