简体   繁体   English

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

[英]Calling get() and put() method of Hashmap correctly.

for the code below: 对于以下代码:

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

//where locksMap is a HashMap with key String (tableName) and value ReentrantReadWriteLock (Lock). //其中locksMap是一个带有键String(tableName)和值ReentrantReadWriteLock(Lock)的HashMap。

My question is, if threads are accessing this method simultaneously, they will get different Lock objects with the same "tableName", because the get and put methods of the Map are called separately. 我的问题是,如果线程同时访问此方法,它们将获得具有相同“tableName”的不同Lock对象,因为Map的get和put方法是分别调用的。

Any solutions with explanation will be appreciated? 任何解释的解决方案将不胜感激? Thanks in advance. 提前致谢。

Using a ConcurrentMap will usually generate better performance than a synchronized block. 使用ConcurrentMap通常会产生比synchronized块更好的性能。

Java 5-7: 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+: Java 8+:

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

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

For one, an implementation like ConcurrentHashMap is documented to not use locks on read operations. 首先,记录了ConcurrentHashMap类的实现,以便不对读取操作使用锁定。 So in this case, where it appears that you intend to get the lock for a single key many more times than you intend to create new locks, that will reduce thread contention. 因此,在这种情况下,如果您打算获得单个密钥的锁定次数比您打算创建新锁定的次数多,那么这将减少线程争用。 If you used synchronized , even if the lock was already created, you're forcing each and every thread to go single file through the critical section. 如果您使用了synchronized ,即使已经创建了锁,您也会强制每个线程通过关键部分单个文件。

Also, implementations can do more advanced forms of locking, or even shard locks so that two writers don't necessarily block each other (if writing to different partitions of the underlying data structure). 此外,实施方式可以做锁定的更高级形式,或甚至碎片锁,以便两位作家不一定彼此阻塞(如果写入底层数据结构的不同分区)。 Again, synchronized uses a single monitor object and can't benefit from knowing the details of the underlying data structure. 同样, synchronized使用单个监视器对象,并且无法从了解底层数据结构的详细信息中受益。

The Java 8 version becomes a one-liner thanks to lambdas and function references. 由于lambda和函数引用,Java 8版本变成了单行。 The ::new syntax refers to the public, no-arg constructor of the adjoining ReentrantReadWriteLock class. ::new语法引用相邻ReentrantReadWriteLock类的public,no-arg构造函数。 The computeIfAbsent method will only invoke that constructor if necessary and basically does all of the boilerplate work in the Java 7 version above for you. computeIfAbsent方法只会在必要时调用该构造函数,并且基本上所有的样板都可以在上面的Java 7版本中工作。 This is particularly useful if the cost of creating the new object is expensive or has unfortunate side effects. 如果创建新对象的成本昂贵或具有不幸的副作用,这尤其有用。 Note that the Java 7 version has to create a new lock instance under certain circumstances and that new object might not ever be used/returned. 请注意,Java 7版本必须在某些情况下创建新的锁定实例,并且可能永远不会使用/返回新对象。

Typically, you would use synchronization to accomplish this. 通常,您将使用同步来完成此操作。 The simplest form of this would be to synchronize the method itself. 最简单的形式是同步方法本身。

public synchronized ReentrantReadWriteLock getLock(String tableName) {

However, if you are concerned about performance I would consider the following approach which uses a synchronized block, but only if the initial lock isn't found. 但是,如果您关注性能,我会考虑使用synchronized块的以下方法,但仅限于未找到初始锁定的情况。

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;
}

You can either modify the method to be synchronized or add a synchronization block inside the method, encompassing both the get() and put() calls. 您可以修改要synchronized的方法,也可以在方法中添加同步块 ,包含get()put()调用。 Note there is a pseudo-pattern (I prefer to call it an idiom) about this, called Double-checked Locking . 请注意,有一个伪模式(我更喜欢称之为习语),称为Double-checked Locking

Another option is to use a ConcurrentMap , which offers the putIfAbsent() method . 另一个选择是使用ConcurrentMap ,它提供putIfAbsent()方法

Note that you will run into lots of discussions/debates about performance of the various options. 请注意,您将遇到有关各种选项性能的大量讨论/争论。 I encourage you to read them with a healthy grain of salt. 我鼓励你用健康的盐阅读它们。 Micro-optimizations and performance analysis is dangerous territory, and often the readability and maintainability of code far outweighs a few microseconds. 微优化和性能分析是危险的领域,代码的可读性和可维护性通常远远超过几微秒。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM