聊聊高并发(十)利用自旋原理来无锁实现“只创建一次”的场景

本文介绍了如何利用自旋原理无锁地实现并发场景下“只创建一次”的需求,以Dubbo框架中的服务注册类为例,详细阐述了自旋锁的实现要点,包括AtomicBoolean、volatile变量和AtomicReference的应用,并提供了一个测试用例证明其正确性。

相信看过之前几篇自旋锁实现的同学对设计一个自旋锁会有一定的感觉,有几个实现的要点很实用:

1. 使用AtomicBoolean原子变量的getAndSet(true)方法来实现并发情况下,找到第一个成功执行方法的线程。这个技巧经常使用,在并发编程中经常会遇到这种需求

2.经常会使用volatile boolean类型的变量在多个线程之间同步状态,需要注意的是,对volatile变量的修改只具有可见性,不具有原子性(比如++操作)。

3. 使用一个AtomicReference原子变量的getAndSet方法来创建一个虚拟的链表结构,原理也是CAS操作,在队列锁中经常使用


这篇结合一个实例来说说如何灵活地利用这些技巧实现无锁的能力。


在实际开发中会经常遇到“只创建一次”的场景,这里说的“只创建一次”不是说单实例模式。单实例模式也是解决只创建一次的问题,关于单实例模式有很多技巧来实现高并发情况下只创建一个对象的问题,这里不讨论。 单实例模式解决的问题是所有线程都公用一个静态的引用,可以用volatile变量来标示这个静态引用,从而在所有线程之间共享这个静态引用的状态是否已经改变。

这里说的“只创建一次”的场景是这个需要创建一次的对象不是直接被全局的引用所引用,而是间接地被引用。经常有这种情况,全局维护一个并发的ConcurrentMap, Map的每个Key对应一个对象,这个对象需要只创建一次。这个场景和单实例模式不一样,不能用volatile变量来维护这个局部对象的引用。


举个实际的例子,下面这段代码来自阿里的Dubbo框架的服务注册类AbstractRegistryFactory。 它维护了一个全局的ConcurrentHashMap来保存服务注册中心字符串和服务注册中心对象的映射,不同的key对应不同的Registry对象。

为了保证每个key对应的Registry对象只创建一次,Dubbo的实现是使用了可重入锁来保证串行性,每次都要先获取锁,然后get(key)来获得Registry对象,如果为空,那么就执行createRegistry()方法来创建唯一的Registry对象。


可重入锁和synchronzied一样,会阻塞线程,在高并发执行这个方法的时候,所有线程排队执行,性能肯定很差。

// 注册中心集合 Map<RegistryAddress, Registry>
    private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();

    public Registry getRegistry(URL url) {
        url = url.setPath(RegistryService.class.getName())
                .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
        String key = url.toServiceString();
        // 锁定注册中心获取过程,保证注册中心单一实例
        LOCK.lock();
        try {
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
       &
### 无算法 CAS 实现原理及代码示例 CAS(Compare-And-Swap)是一种原子操作,广泛应用于无编程中以实现线程安全的并发控制。其核心思想是通过硬件指令保证操作的原子性,从而避免传统机制中的线程阻塞和死问题[^1]。 #### CAS 的基本原理 CAS 操作包含三个参数:内存地址、预期值和新值。具体过程如下: - 比较内存地址中的值是否与预期值相等。 - 如果相等,则将内存地址中的值替换为新值,并返回 `true`。 - 如果不相等,则什么都不做,直接返回 `false`。 这种操作在硬件层面被设计为原子操作,确保了多线程环境下的安全性[^3]。 #### CAS 的伪代码表示 以下是 CAS 操作的伪代码表示: ```python boolean compareAndSwap(int *address, int expected, int new_value) { if (*address == expected) { *address = new_value; return true; } return false; } ``` 这段伪代码展示了 CAS 的基本逻辑:比较内存地址中的值与预期值是否一致,若一致则更新为新值[^1]。 #### CAS 在无队列中的应用 在无队列实现中,CAS 被用来确保多个线程同时对队列进行操作时不会出现冲突。例如,在入队操作中,线程需要确保当前尾节点的 `next` 指针为空,然后使用 CAS 将新节点挂载到尾节点上。如果 CAS 成功,则更新尾节点指针;否则,重试直到成功[^4]。 以下是一个基于 CAS 的无队列入队操作的伪代码示例: ```python class Node: def __init__(self, value): self.value = value self.next = None class LockFreeQueue: def __init__(self): self.head = self.tail = Node(None) def enqueue(self, value): new_node = Node(value) while True: tail = self.tail next_node = tail.next if next_node is None: if cas(tail.next, None, new_node): # 使用 CAS 更新尾节点的 next 指针 if tail == self.tail: # 确保尾节点未被其他线程修改 self.tail = new_node # 更新尾节点指针 return else: cas(self.tail, tail, next_node) # 更新尾节点指针以追上实际尾节点 ``` #### CAS 的优点与局限性 ##### 优点 - **无并发**:通过硬件指令保证操作的原子性,无需传统的机制,避免线程阻塞和死问题。 - **高效性**:CAS 是硬件层面的操作,比软件层面的具有更低的开销,尤其在多核处理器中能够充分利用并行能力。 - **灵活性**:CAS 可作为构建其他复杂无数据结构(如队列、栈)的基础操作[^1]。 ##### 局限性 - **ABA 问题**:当某个值从 A 变为 B 再变回 A 时,CAS 无法检测到中间的变化。 - **高竞争场景下的性能下降**:在高竞争场景下,线程可能需要多次重试 CAS 操作,导致性能下降[^3]。 #### 总结 CAS 是一种高效的无操作,适用于简单的数值更新或构建复杂无数据结构。然而,在选择使用 CAS 时,需要权衡其优缺点,尤其是在高竞争场景或可能存在 ABA 问题的情况下[^2]。
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值