You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java多线程环境下如何实现存储类的细粒度线程安全?

基于值对象的细粒度锁实现线程安全泛型存储类的问题与解决方案

问题分析

你希望用基于值对象的细粒度锁替代粗粒度的对象锁,避免不同值的add/delete/search操作互斥,提升并发性能。但这里存在一个容易忽略的关键漏洞:BST的操作会遍历从根节点到目标节点的路径,不同值的操作可能会修改路径上的同一个父节点。比如添加8时需要修改9的左指针,而删除9时需要修改9的父节点指针,这两个操作同时执行会导致BST结构损坏,仅对目标值加锁无法解决这个问题。

下面提供三种可行方案,覆盖不同场景需求:

方案一:基于值对象的锁(适配你的初始设想)

如果业务场景可以接受BST结构的并发风险,或者仅需保证同一值的操作互斥,可采用此方案:

代码实现

import java.util.concurrent.ConcurrentHashMap;

class MyStorage<T extends Comparable<T>> {
    private BSTreeNode rootNode;
    // 线程安全的映射表,存储每个值对应的锁对象
    private final ConcurrentHashMap<T, Object> valueLocks = new ConcurrentHashMap<>();

    private class BSTreeNode {
        T value;
        int occurrence;
        BSTreeNode left;
        BSTreeNode right;

        BSTreeNode(T value) {
            this.value = value;
            this.occurrence = 1;
        }
    }

    // 原子化获取或创建值对应的锁
    private Object getValueLock(T value) {
        Object lock = valueLocks.putIfAbsent(value, new Object());
        return lock == null ? valueLocks.get(value) : lock;
    }

    public boolean add(T value) {
        Object lock = getValueLock(value);
        synchronized (lock) {
            if (rootNode == null) {
                rootNode = new BSTreeNode(value);
                return true;
            }
            BSTreeNode current = rootNode;
            BSTreeNode parent = null;
            while (current != null) {
                parent = current;
                int cmp = value.compareTo(current.value);
                if (cmp == 0) {
                    current.occurrence++;
                    return true;
                }
                current = cmp < 0 ? current.left : current.right;
            }
            // 添加新节点到对应位置
            if (value.compareTo(parent.value) < 0) {
                parent.left = new BSTreeNode(value);
            } else {
                parent.right = new BSTreeNode(value);
            }
            return true;
        }
    }

    public boolean delete(T value) {
        Object lock = getValueLock(value);
        synchronized (lock) {
            BSTreeNode parent = null;
            BSTreeNode current = rootNode;
            while (current != null && value.compareTo(current.value) != 0) {
                parent = current;
                current = value.compareTo(current.value) < 0 ? current.left : current.right;
            }
            if (current == null) return false;
            // 处理重复值
            if (current.occurrence > 1) {
                current.occurrence--;
                return true;
            }
            // 删除无后代的节点(复杂子节点迁移逻辑省略)
            if (current.left == null && current.right == null) {
                if (parent == null) rootNode = null;
                else if (parent.left == current) parent.left = null;
                else parent.right = null;
            }
            return true;
        }
    }

    public boolean search(T value) {
        Object lock = getValueLock(value);
        synchronized (lock) {
            BSTreeNode current = rootNode;
            while (current != null) {
                int cmp = value.compareTo(current.value);
                if (cmp == 0) return current.occurrence > 0;
                current = cmp < 0 ? current.left : current.right;
            }
            return false;
        }
    }
}

关键注意事项

  • 值对象必须正确实现equals和hashCode:ConcurrentHashMap依赖这两个方法区分不同值,错误实现会导致锁混乱,破坏线程安全。
  • 内存占用问题:valueLocks会保留所有曾添加过的值的锁,即使值被删除也不会自动释放。若内存压力大,可改用WeakHashMap配合同步块(需自行处理线程安全)。
  • BST结构风险:不同值的操作可能修改同一父节点,导致结构不一致,此方案仅适合对结构一致性要求较低的场景。

方案二:节点级细粒度锁(保证BST结构安全)

如果需要严格保证BST的并发结构安全,可采用节点级锁:遍历路径时按顺序锁定节点,避免死锁,同时保证细粒度并发。

代码示例(简化版)

class MyStorage<T extends Comparable<T>> {
    private BSTreeNode rootNode;

    private class BSTreeNode {
        T value;
        int occurrence;
        BSTreeNode left;
        BSTreeNode right;
        // 每个节点自带锁对象
        final Object lock = new Object();

        BSTreeNode(T value) {
            this.value = value;
            this.occurrence = 1;
        }
    }

    public boolean add(T value) {
        // 处理根节点为空的情况
        if (rootNode == null) {
            synchronized (this) {
                if (rootNode == null) {
                    rootNode = new BSTreeNode(value);
                    return true;
                }
            }
        }
        BSTreeNode current = rootNode;
        synchronized (current.lock) {
            while (true) {
                int cmp = value.compareTo(current.value);
                if (cmp == 0) {
                    current.occurrence++;
                    return true;
                }
                BSTreeNode next = cmp < 0 ? current.left : current.right;
                if (next == null) {
                    // 添加新节点
                    BSTreeNode newNode = new BSTreeNode(value);
                    if (cmp < 0) current.left = newNode;
                    else current.right = newNode;
                    return true;
                }
                // 先锁子节点再释放父节点,避免死锁
                synchronized (next.lock) {
                    current.lock.notifyAll();
                    current = next;
                }
            }
        }
    }

    // delete、search方法遵循相同的节点锁顺序逻辑,此处省略
}

方案三:使用Java内置并发有序集合(最简方案)

如果不需要手动实现BST,直接用ConcurrentSkipListMap是最优选择——它是线程安全的有序集合,基于跳表实现,并发性能优于手动实现的BST。

代码示例

import java.util.concurrent.ConcurrentSkipListMap;

class MyStorage<T extends Comparable<T>> {
    private final ConcurrentSkipListMap<T, Integer> map = new ConcurrentSkipListMap<>();

    public boolean add(T value) {
        map.merge(value, 1, Integer::sum);
        return true;
    }

    public boolean delete(T value) {
        return map.computeIfPresent(value, (k, v) -> v > 1 ? v - 1 : null) != null;
    }

    public boolean search(T value) {
        return map.containsKey(value);
    }
}

内容的提问来源于stack exchange,提问作者kesarling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:10:21