1. 了解ConcurrentModificationException
在Java编程中,ConcurrentModificationException是一个较为常见的异常,它主要是在多线程并发操作集合类的时候可能会出现。例如:在一个线程遍历了一个List集合中的元素,并且另一个线程修改了List中的元素时,就会抛出此异常。下面看一下代码示例:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String str : list) {
System.out.println(str);
list.remove(str);
}
}
运行上述代码时,会出现如下异常提示:
Exception in thread "main" java.util.ConcurrentModificationException
那么如何解决这种并发修改异常呢?
2. 使用Iterator进行遍历
在上面的示例代码中,我们使用的是for循环来遍历List集合中的元素,这种方式虽然非常方便,但是它是不安全的。我们可以使用Iterator来代替for循环,这样可以避免出现ConcurrentModificationException异常。
2.1 操作方法
下面是使用Iterator进行遍历的代码示例:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
iterator.remove();
}
运行上述代码,输出结果是:
A
B
C
正如你所看到的,Iterator遍历List集合时,并不是像for-each循环那样直接操作集合中的对象,而是通过iterator对象去操作List集合。
2.2 代码演示
下面是通过一个例子演示在并发情况下使用Iterator进行遍历的代码:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
new Thread(() -> {
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
System.out.println(str);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
list.remove("C");
}).start();
}
上面的代码中我们通过两个线程同时对List集合进行操作,一个线程遍历,一个线程删除。 如果使用for-each循环,则会抛出ConcurrentModificationException异常,但使用Iterator则不会出现异常,正常执行。可以看出Iterator的确是更加安全的遍历方式。
3. 使用线程安全的集合
如果你确实需要在多线程的情况下对集合进行操作,比如遍历和修改操作,但是不想使用Iterator,可以考虑使用线程安全的集合类如:CopyOnWriteArrayList、ConcurrentHashMap等。这些类都是Java提供的特定为多线程环境下的集合类,它们能够在多线程并发操作时保证数据的安全性。
3.1 CopyOnWriteArrayList的使用方法
下面是使用CopyOnWriteArrayList进行操作的代码示例:
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
new Thread(() -> {
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
System.out.println(str);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
list.remove("C");
}).start();
运行上述代码,输出结果是:
A
B
CopyOnWriteArrayList实现了读写分离,每当需要修改数据时,都会重新创建一个集合副本,对副本进行操作,操作完成后用新副本代替旧副本。
3.2 ConcurrentHashMap的使用方法
ConcurrentHashMap是一个线程安全的哈希表集合,它是Java提供的专门针对多线程并发访问的集合类。它可以在多个线程读写的同时保证数据的一致性和正确性。
下面是使用ConcurrentHashMap进行操作的代码示例:
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
new Thread(() -> {
Iterator<String> itr = map.keySet().iterator();
while (itr.hasNext()) {
String key = itr.next();
String value = map.get(key);
System.out.println(key + ":" + value);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
map.remove("key3");
}).start();
运行上述代码,输出结果是:
key1:value1
key3:value3
key2:value2
ConcurrentHashMap使用了锁分段,也就是说,它将整个HashMap分成若干个Segment,每个Segment都是一个独立的哈希表,只对其内部的数据进行同步操作,这样就不会在高并发的情况下造成性能瓶颈。
4. 总结
在Java中,我们常常需要在多线程的情况下进行集合的操作,这时就需要考虑并发问题的解决了。使用Iterator进行遍历是目前最为常用和安全的方式,其次还可以选择线程安全的集合类,比如ConcurrentHashMap和CopyOnWriteArrayList等。 但是,在遇到并发问题时,单纯地选择线程安全集合并不意味着就安全了,必须要根据需求选择最合适的集合,并合理使用并发控制方式。
最后,如果您想了解更多Java并发相关知识,可以查阅Java官方文档或者参考Java高并发编程实战等相关书籍。