我有一种方法应该从队列中获取地图,并且仅在地图大小未超过一定数量时才这样做。这提示了并发问题,因为我从每个线程获得的大小是不一致的全局变量。我通过这段代码复制了问题
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
方法的语义(例如size
和isEmpty
)的语义已略微减弱,以反映集合的并发性质。由于大小的结果在计算时可能已经过时,因此它实际上仅是估计值,因此允许大小返回近似值而不是精确计数。乍一看这似乎令人不安,但实际上,诸如size
和isEmpty
之类的方法在并发环境中的用处不大,因为这些数量是移动目标。因此,削弱了这些操作的要求,以便能够对最重要的操作(主要是get
,put
,containsKey
和remove
)进行性能优化。synchronized
Map
实现提供的一项功能而不是ConcurrentHashMap
提供的一项功能是可以锁定地图以进行独占访问。使用Hashtable
和synchronizedMap
,获取Map锁可防止任何其他线程访问它。在异常情况下,例如原子地添加多个映射,或多次迭代Map并需要以相同的顺序查看相同的元素,这可能是必要的。总的来说,这是一个合理的权衡:应该期望并发集合不断更改其内容。
解决方案:
重构设计,不要在并发访问中使用size
方法。
要将方法用作size
和isEmpty
,可以使用同步集合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();
}
}
类BoundConcurrentHashMap
与ConcurrentHashMap
一样有效,并且几乎是线程安全的。因为在remove
方法中删除元素和释放信号量并没有同时发生。但是在这种情况下是可以容忍的。 size
方法仍返回近似值,但put
方法将不允许超出地图大小。
我的模式类似于OR:“word1 | word2 | word3”我大约有800个字。可能有问题吗? 参考方案 您仅受记忆和理智的限制。 :)
Java:线程池如何将线程映射到可运行对象 - java试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …
JAVA:字节码和二进制有什么区别? - javajava字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…
java:继承 - java有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…
Java:BigInteger,如何通过OutputStream编写它 - java我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…