这个线程在Java中安全高效吗

时间:2019-03-07 10:56:01

标签: java multithreading concurrency thread-safety concurrenthashmap

public class Test {

static ConcurrentHashMap<Integer, Integer> map = null;
final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public static void open() {

    lock.writeLock().lock();
    try {
        if (map != null) {
            return;
        }
        map = new ConcurrentHashMap<>();
    } finally {
        lock.writeLock().unlock();
    }
}

public static void close() {

    final ConcurrentHashMap<Integer, Integer> concurrentHashMap;

    lock.writeLock().lock();
    try {
        if (map == null) {
            return;
        }
        concurrentHashMap = map;
        map = null;
    } finally {
        lock.writeLock().unlock();
    }

    // deal with concurrentHashMap data
}

public static boolean put(final int key, final int value) {
    lock.readLock().lock();
    try {
        if (map == null) {
            return false;
        }
        if (map.putIfAbsent(key, value) != null) {
            return false;
        }
    } finally {
        lock.readLock().unlock();
    }
    return true;
}

public static boolean remove(final int key) {
    lock.readLock().lock();
    try {
        if (map == null) {
            return false;
        }
        if (map.remove(key) == null) {
            return false;
        }
    } finally {
        lock.readLock().unlock();
    }
    return true;
}

}

在上面的代码中,当put()和remove()时,使用readLock而不是writeLock,它们是最常用的;当open()和close()时,它们都使用writeLock,它们的使用率较低。目标是提高并发性。我不确定:

  1. 线程安全吗?
  2. 效率高吗?

我认为两者都是对的。我知道ConcurrentHashMap是线程安全的。我想知道此实施的好坏,为什么。

2 个答案:

答案 0 :(得分:2)

线程安全:

从某种意义上说,它是线程安全的。调用close之后,对putremove的进一步调用将不会影响concurrentHashMap所引用的地图的状态。

但是,在下一个put之前调用removeopen会导致更新丢失。鉴于openclose的表面意义是要避免丢失更新,这令我感到不满意。这可能是另一个级别的线程安全问题。

效率:

一方面:我观察到在您持有锁的同时对地图进行了所有更新。鉴于此,我认为使用ConcurrentHashMap没有任何意义。使用常规的HashMap是线程安全的,并且效率更高。

另一方面,由于所有更新都是在持有锁的同时执行的,因此锁是一个并发瓶颈,使用ConcurrentHashMap的潜在并发优势还没有意义。


我想我将使用AtomicReferencejavadoc)...且不加锁来实现这一点。诀窍是使用ref.getAndSet(new ConcurrentHashMap())将现有地图“切换”为新的空地图。

AtomicReference仍然是并发瓶颈,但程度要小得多,并且可以通过将这两个操作作为一个原子操作来避免“关闭...打开”漏洞。

有关使用AtomicReference的示例解决方案,请参阅@Holger的答案...。请注意,他的版本无法解决“封闭...裸眼”问题。

答案 1 :(得分:2)

正如其他人所说,通过在此用例中使用AtomicReference可以提高效率。但是,更重要的是,代码变得更加简单:

static final AtomicReference<ConcurrentHashMap<Integer, Integer>>
    MAP = new AtomicReference<>();

public static void open() {
    MAP.compareAndSet(null, new ConcurrentHashMap<>());
}

public static void close() {
    ConcurrentHashMap<Integer, Integer> map = MAP.getAndSet(null);
    if(map != null) {
        // deal with map data
    }
}

public static boolean put(final int key, final int value) {
    ConcurrentHashMap<Integer, Integer> map = MAP.get();
    return map != null && map.putIfAbsent(key, value) == null;
}

public static boolean remove(final int key) {
    ConcurrentHashMap<Integer, Integer> map = MAP.get();
    return map != null && map.remove(key) != null;
}