多个线程检查地图大小和conccurency

时间:2016-06-07 22:37:15

标签: java java-ee concurrency concurrenthashmap

我有一个方法可以从队列中提供地图,只有在地图大小不超过某个数字时才会这样做。这引发了并发问题,因为我从每个线程获得的大小都是非连贯的全局。我通过这段代码复制了这个问题

import java.sql.Timestamp;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrenthashMapTest {
private ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>();
private ThreadUx[] tArray = new ThreadUx[999];

public void parallelMapFilling() {
for ( int i = 0; i < 999; i++ ) {
    tArray[i] = new ThreadUx( i );
}
for ( int i = 0; i < 999; i++ ) {
    tArray[i].start();
}
}

public class ThreadUx extends Thread {
private int seq = 0;

public ThreadUx( int i ) {
    seq = i;
}

@Override
public void run() {
    while ( map.size() < 2 ) {
    map.put( seq, seq );
    System.out.println( Thread.currentThread().getName() + " || The size is: " + map.size() + " || " + new Timestamp( new Date().getTime() ) );
    }
}
}

public static void main( String[] args ) {
new ConcurrenthashMapTest().parallelMapFilling();
}
}

通常我应该只有一行输出且大小不超过1,但我确实有这样的东西

Thread-1 || The size is: 2 || 2016-06-07 18:32:55.157
Thread-0 || The size is: 2 || 2016-06-07 18:32:55.157

我尝试将整个run方法标记为已同步,但只有在我执行此操作时才会起作用

@Override
    public void run() {
        synchronized ( map ) {
        if ( map.size() < 1 ) {
            map.put( seq, seq );
            System.out.println( Thread.currentThread().getName() + " || The size is: " + map.size() + " || " + new Timestamp( new Date().getTime() ) );
        }
        }
    }

它有效,为什么只有同步块工作和同步方法?此外,我不想使用与同步块一样旧的东西,因为我正在使用Java EE应用程序,是否有Spring或Java EE任务执行器或注释​​可以提供帮助?

2 个答案:

答案 0 :(得分:3)

您正在使用site:elastic.co/blog/,并根据API文档:

  

请记住聚合状态方法的结果包括    size ,isEmpty和containsValue通常仅在地图时才有用   没有在其他线程中进行并发更新。否则   这些方法的结果反映了可能足够的瞬态   用于监控或估算目的,但不用于程序控制。

这意味着除非您明确同步ConcurrentHashMap的访问权限,否则无法获得准确的结果。

size()添加到synchronized方法不起作用,因为线程没有在同一个锁对象上进行同步 - 每个锁都会自动锁定。

在地图上进行同步本身确实有效,但恕我直言,这不是一个好选择,因为这会失去run可以提供的性能优势。

总之,您需要重新考虑设计。

答案 1 :(得分:3)

来自Java Concurrency in Practice

  

在整个地图上运行的ConcurrentHashMap方法的语义,例如sizeisEmpty,已经略微削弱,以反映集合的并发性质。由于大小的结果在计算时可能已经过时,因此它实际上只是一个估计值,因此允许大小返回近似值而不是精确计数。虽然起初这可能看起来很令人不安,但实际上sizeisEmpty等方法在并发环境中的用处要小得多,因为这些数量是移动目标。因此,这些操作的要求被削弱,以便为最重要的操作(主要是getputcontainsKeyremove启用性能优化。

     

synchronized Map实现提供但不是ConcurrentHashMap提供的一项功能是锁定地图以进行独占访问。使用HashtablesynchronizedMap,获取Map锁可防止任何其他线程访问它。在异常情况下这可能是必要的,例如以原子方式添加多个映射,或者多次迭代Map并且需要以相同的顺序查看相同的元素。但总的来说,这是一个合理的权衡:应该预期并发收集会不断改变其内容。

解决方案:

  1. 重构设计,不要将size方法用于并发访问。

  2. 要将方法用作sizeisEmpty,您可以使用同步收集Collections.synchronizedMap。同步集合通过序列化对集合状态的所有访问来实现其线程安全性。这种方法的成本是并发性差;当多个线程争用集合范围的锁时,吞吐量会受到影响。此外,您还需要将其检查和放置的块与map实例同步,因为它是复合操作。

  3. 第三。使用第三方实现或编写自己的实现。

    public class BoundConcurrentHashMap <K,V> {
        private final Map<K, V> m;
        private final Semaphore semaphore;
        public BoundConcurrentHashMap(int size) {
            m = new ConcurrentHashMap<K, V>();
            semaphore = new Semaphore(size);
        }
    
        public V get(V key) {
            return m.get(key);
        }
    
        public boolean put(K key, V value) {
            boolean hasSpace = semaphore.tryAcquire();
            if(hasSpace) {
                m.put(key, value);
            }
            return hasSpace;
        }
    
        public void remove(Object key) {
            m.remove(key);
            semaphore.release();
        }
    
        // approximation, do not trust this method
        public int size(){
            return m.size();
        }
    }
    

    BoundConcurrentHashMapConcurrentHashMap一样有效且几乎是线程安全的。因为删除元素并在remove方法中释放信号量不是应该同时进行的。但在这种情况下,它是可以容忍的。 size方法仍会返回近似值,但put方法不允许超出地图大小。