@Override
    public Difference<Node> add(Node nodeWithoutInheritanceOrigin) {
        Node node = addInheritanceOrigin(nodeWithoutInheritanceOrigin);

        ImmutableContextSet context = node.getContexts();
        Difference<Node> result = new Difference<>();

        this.lock.lock();
        try {
            SortedSet<Node> nodes = this.map.computeIfAbsent(context, VALUE_SET_SUPPLIER);

            // add the new node to the set - if it was already there, return
            if (!nodes.add(node)) {
                return result;
            }

            // mark that we added the node in the results
            result.recordChange(ChangeType.ADD, node);

            // remove any others that were in the set already with a different value/expiry time
            removeMatchingButNotSame(nodes.iterator(), node, result);

            // update the inheritanceMap too if necessary
            if (node instanceof InheritanceNode) {
                SortedSet<InheritanceNode> inhNodes = this.inheritanceMap.computeIfAbsent(context, INHERITANCE_VALUE_SET_SUPPLIER);
                // remove existing..
                inhNodes.removeIf(el -> node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
                // .. & add
                if (node.getValue()) {
                    inhNodes.add((InheritanceNode) node);
                }
            }

        } finally {
            this.lock.unlock();
        }

        return result;
    }
This lock is used whilst performing mutations, but *not* reads.

The maps themselves are thread safe, so for querying, we just allow
the read methods to do whatever they want without any locking.
However, we want mutations to be atomic, so we use the lock to ensure that happens.
common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java