正确调用Hashmap的get()和put()方法。

时间:2014-11-24 19:53:31

标签: java multithreading concurrency hashmap

代码如下:

public ReentrantReadWriteLock getLock(String tableName) {
    ReentrantReadWriteLock lock = locksMap.get(tableName);
    if (lock == null) {
        lock = new ReentrantReadWriteLock();
        locksMap.put(tableName, lock);
    }
}

//其中locksMap是一个带有键字符串(tableName)和值ReentrantReadWriteLock(Lock)的HashMap。

我的问题是,如果线程同时访问此方法,它们将获得具有相同“tableName”的不同Lock对象,因为Map的get和put方法是单独调用的。

任何有解释的解决方案都将受到赞赏? 提前谢谢。

3 个答案:

答案 0 :(得分:3)

使用ConcurrentMap通常会产生比synchronized阻止更好的性能。

Java 5-7:

ConcurrentMap<String, ReadWriteLock> lockMap = new ConcurrentHashMap<>();

ReadWriteLock getLock(String key) {
    ReadWriteLock lock = lockMap.get(key);
    if (lock == null) {
        lock = new ReentrantReadWriteLock();
        ReadWriteLock other = lockMap.putIfAbsent(key, lock);
        if (other != null) {
            // another thread's putIfAbsent won
            lock = other;
        }
    }
    return lock;
}

Java 8 +:

ConcurrentMap<String, ReadWriteLock> lockMap = new ConcurrentHashMap<>();

ReadWriteLock getLock(String key) {
    return lockMap.computeIfAbsent(key, ReentrantReadWriteLock::new);
}

首先,记录了ConcurrentHashMap之类的实现,以便不对读取操作使用锁定。因此,在这种情况下,如果您打算获得单个密钥的锁定次数比您打算创建新锁定的次数多,那么这将减少线程争用。如果您使用synchronized,即使已经创建了锁,您也会强制每个线程通过关键部分单个文件。

此外,实现可以执行更高级的锁定形式,甚至是 shard 锁定,这样两个编写器就不必相互阻塞(如果写入底层数据结构的不同分区)。同样,synchronized使用单个监视器对象,无法从了解基础数据结构的详细信息中获益。

由于lambdas和函数引用,Java 8版本变成了单行。 ::new语法引用相邻ReentrantReadWriteLock类的public,no-arg构造函数。 computeIfAbsent方法只会在必要时调用该构造函数,并且基本上所有的样板都可以在上面的Java 7版本中工作。如果创建新对象的成本昂贵或具有不幸的副作用,这尤其有用。请注意,Java 7版本具有以在某些情况下创建新的锁定实例,并且可能不会使用/返回新对象。

答案 1 :(得分:0)

通常,您将使用同步来完成此操作。最简单的形式是同步方法本身。

public synchronized ReentrantReadWriteLock getLock(String tableName) {

但是,如果您担心性能,我会考虑使用同步块的以下方法,但仅限于未找到初始锁定。

public ReentrantReadWriteLock getLock(String tableName) {
    ReentrantReadWriteLock lock = locksMap.get(tableName);

    if (lock != null) {
        return lock;
    }

    synchronized(locksMap) {
        lock = locksMap.get(tableName);
        if (lock == null) {
            lock = new ReentrantReadWriteLock();
            locksMap.put(tableName, lock);
        }
    }
    return lock;
}

答案 2 :(得分:0)

您可以将方法修改为synchronized,也可以在方法中添加synchronization block,同时包含get()put()来电。请注意,有一个伪模式(我更喜欢称之为习语),称为Double-checked Locking

另一个选择是使用ConcurrentMap,它提供putIfAbsent() method

请注意,您将讨论有关各种选项的性能的大量讨论/辩论。我鼓励你用健康的盐阅读它们。微优化和性能分析是危险的领域,代码的可读性和可维护性通常远远超过几微秒。