在本文中,我们将讨论如何使用ConcurrentHashMap通过2个不同的线程同时实现读取(迭代)和修改(删除/添加)操作,而这是简单的HashMap无法实现的
1. HashMap:
如果2个不同的线程同时对同一个HashMap对象执行操作,则编译器将引发ConcurrentModificationException
我们将演示一个使用HashMap的简单示例,该示例执行
- 1个第一螺纹迭代或读取的条目一个接一个
- 2第二螺纹去除键-值对; 而其他线程正在迭代HashMap对象
IterateAndModifyHashMapSimultaneously.java
1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package in.bench.resources.concurrent.collection;
import java.util.HashMap;
import java.util.Map;
// extending Thread class
public class IterateAndModifyHashMap extends Thread {
// creating HashMap object of type <Integer, String>
static HashMap<Integer, String> hm =
new HashMap<Integer, String>();
@Override
public void run() {
try {
// sleeping thread for 1000 ms
Thread.sleep( 1000 );
// removing entry with key=1
String value = hm.remove( 1 );
System.out.println( "Entry with {key=1" +
" & value=" + value + "} is removed" );
}
catch (InterruptedException iex) {
iex.printStackTrace();
}
System.out.println( "Removal is done... !!" );
}
/**
* main() method
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// adding key-value pairs to HashMap object
hm.put( 1 , "google.com" );
hm.put( 2 , "youtube.com" );
hm.put( 3 , "facebook.com" );
// creating another thread
Thread newThread = new Thread( new IterateAndModifyHashMap());
newThread.start();
// iterating HM object using enhanced for-loop
for (Map.Entry<Integer, String> me : hm.entrySet()) {
System.out.println( "{Key=" + me.getKey()
+ "\t" + "Value=" + me.getValue() + "}" );
// sleeping thread for 1500 ms, after every turn
Thread.sleep( 1500 );
}
System.out.println( "Iterating completed... !!" );
}
}
|
输出:
1个
2
3
4
5
6
7
8
9
|
{Key=1 Value=google.com}
Entry with {key=1 & value=google.com} is removed
Removal is done... !!
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:895)
at java.util.HashMap$EntryIterator.next(HashMap.java:935)
at java.util.HashMap$EntryIterator.next(HashMap.java:933)
at in.bench.resources.concurrent.collection
.IterateAndModifyHashMap.main(IterateAndModifyHashMap.java:48)
|
说明:
- 主线程迭代HashMap对象,子线程使用key = 1删除HashMap条目
- 从输出中可以清楚地看到,当一个线程在HashMap对象上进行迭代,并且是否有其他线程执行修改操作时(即,在同一HashMap对象上,另一个线程正在删除条目)
- 然后编译器将抛出ConcurrentModificationException
- 注意:介绍学习示例的sleep(ms)
- 因为没有睡眠,两个线程都将独立执行(在nano / pico秒内完成其执行),并且不会出现任何编译时错误
- 由于我们试图用少量的数据来理解(执行在十亿分之一秒内完成)
- 但是,由于有大量数据,不需要引入睡眠概念
- 随着每个线程执行时间的增加,肯定会引发ConcurrentModificationException
问)如何克服上述HashMap异常?
- 随着ConcurrentHashMap中,我们可以克服这个问题
- 因为它适用于不同的锁定策略 或不同的并发级别
2. ConcurrentHashMap:
当2个不同的线程同时对同一个ConcurrentHashMap对象执行操作时,编译器将不会引发任何运行时异常
这是使用ConcurrentHashMap而不是HashMap的优点
在演示示例中,
- 1日线程迭代通过的ConcurrentHashMap的所有键值对
- 虽然其他线程可以安全地删除key = 1的键/值对
- 与HashMap不同,编译器不会引发任何ConcurrentModificationException
- 这是因为ConcurrentHashMap在不同的并发级别或不同的锁定策略下工作
IterateAndModifyConcurrentHashMapSimultaneously.java
1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
package in.bench.resources.concurrent.collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// implementing Runnable interface
public class IterateAndModifyConcurrentHashMap implements Runnable {
// creating ConcurrentHashMap object of type <Integer, String>
static ConcurrentHashMap<Integer, String> chm =
new ConcurrentHashMap<Integer, String>();
@Override
public void run() {
try {
// sleeping thread for 1000 ms
Thread.sleep( 1000 );
// removing entry with key=1
String value = chm.remove( 1 );
System.out.println( "Entry with {key=1"
+ " & value=" + value + "} is removed" );
}
catch (InterruptedException iex) {
iex.printStackTrace();
}
System.out.println( "Removal is done... !!" );
}
/**
* main() method
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// adding key-value pairs to ConcurrentHashMap object
chm.put( 1 , "google.com" );
chm.put( 2 , "youtube.com" );
chm.put( 3 , "facebook.com" );
// creating another thread
Thread newThread = new Thread(
new IterateAndModifyConcurrentHashMap());
newThread.start();
// iterating CHM object using enhanced for-loop
for (Map.Entry<Integer, String> me : chm.entrySet()) {
System.out.println( "{Key=" + me.getKey()
+ "\t" + "Value=" + me.getValue() + "}" );
// sleeping thread for 2000 ms, after every turn
Thread.sleep( 2000 );
}
System.out.println( "Iterating completed... !!" );
}
}
|
输出:
1个
2
3
4
5
|
{Key=3 Value=facebook.com}
Entry with {key=1 & value=google.com} is removed
Removal is done... !!
{Key=2 Value=youtube.com}
Iterating completed... !!
|
说明:
- 当我们执行相同的程序用ConcurrentHashMap替换HashMap时,程序执行时没有任何运行时异常,例如ConcurrentModificationException
- 但是在不同的执行点可能会有不同的输出
- 原因:因为,当一个线程迭代 所有条目时, 它可能会 从第二个线程获得 更新的条目
- 在上面的例子中,我们得到了更新的条目,这是可能的,因为1个第一螺纹,其是迭代得到更新用从2次螺纹(去除)
- 下一次迭代也是如此,因为下一次可能会迭代所有条目(在这种情况下,第一个不会从第二个线程进行更新)
让我们也打印其他可能性
输出:
1个
2
3
4
5
6
|
{Key=1 Value=google.com}
Entry with {key=1 & value=google.com} is removed
Removal is done... !!
{Key=2 Value=youtube.com}
{Key=3 Value=facebook.com}
Iterating completed... !!
|
从上面的输出,很显然,1个第一线程没有得到更新用来自2次螺纹和1条第一线穿过的所有条目迭代