多线程检查映射大小和并发 - java

我有一种方法应该从队列中获取地图,并且仅在地图大小未超过一定数量时才这样做。这提示了并发问题,因为我从每个线程获得的大小是不一致的全局变量。我通过这段代码复制了问题

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

我试图将整个运行方法标记为已同步,但这只有在我这样做时才起作用

@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() ) );
        }
        }
    }

它起作用了,为什么只有synch块和synch方法起作用了?另外,我也不想使用像在Java EE应用程序上工作时一样古老的同步块,是否有Spring或Java EE任务执行器或注释​​可以提供帮助?

参考方案

从Java Concurrency in Practice:

在整个Map上运行的ConcurrentHashMap方法的语义(例如sizeisEmpty)的语义已略微减弱,以反映集合的并发性质。由于大小的结果在计算时可能已经过时,因此它实际上仅是估计值,因此允许大小返回近似值而不是精确计数。乍一看这似乎令人不安,但实际上,诸如sizeisEmpty之类的方法在并发环境中的用处不大,因为这些数量是移动目标。因此,削弱了这些操作的要求,以便能够对最重要的操作(主要是getputcontainsKeyremove)进行性能优化。
synchronized Map实现提供的一项功能而不是ConcurrentHashMap提供的一项功能是可以锁定地图以进行独占访问。使用HashtablesynchronizedMap,获取Map锁可防止任何其他线程访问它。在异常情况下,例如原子地添加多个映射,或多次迭代Map并需要以相同的顺序查看相同的元素,这可能是必要的。总的来说,这是一个合理的权衡:应该期望并发集合不断更改其内容。

解决方案:

重构设计,不要在并发访问中使用size方法。

要将方法用作sizeisEmpty,可以使用同步集合Collections.synchronizedMap。同步的集合通过序列化对集合状态的所有访问来实现其线程安全。这种方法的代价是并发性差;当多个线程争用集合范围的锁定时,吞吐量会受到影响。另外,由于这是一个复合操作,因此还需要将其检查并放入地图实例的块同步。

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

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方法将不允许超出地图大小。

Java:正则表达式模式匹配器是否有大小限制? - java

我的模式类似于OR:“word1 | word2 | word3”我大约有800个字。可能有问题吗? 参考方案 您仅受记忆和理智的限制。 :)

Java:线程池如何将线程映射到可运行对象 - java

试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

JAVA:字节码和二进制有什么区别? - java

java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Java:BigInteger,如何通过OutputStream编写它 - java

我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…