Java Collections(集合) Bug
1.尝试在遍历中修改集合元素
假设现在公司经营不善,我们要解雇 32 岁的工程师 David。
List<String> engineers = new ArrayList<>(asList("Tom", "David"));
System.out.println(engineers);
for (String engineer : engineers) {
if ("David".equalsIgnoreCase(engineer)) {
engineers.remove(engineer);
}
}
System.out.println(engineers);
大家都知道上面的代码会运行出错(java.util.ConcurrentModificationException),出现了两个 Object(Iterator 和 Collections) 我们可以使用 Iterator 来解决
List<String> engineers = new ArrayList<>(asList("Tom", "David"));
System.out.println(engineers);
for (Iterator<String> iterator = engineers.iterator(); iterator.hasNext();) {
String next = iterator.next();
if ("David".equalsIgnoreCase(next)) {
iterator.remove();
}
}
System.out.println(engineers);
}
但是 8012 年了,这种方法是不是有一点 old school 呢?我们有更时尚一点的做法!在 Java 8 中引入了 Lambda 表达式,我们只需要一行代码即可。
engineers.removeIf(engineer -> "David".equalsIgnoreCase(engineer));
2.集合元素并发修改
假设公司经营不善,凡是受到 1000 次投诉以上的工程师要被解雇
private static final String NAME = "David";
private static final int TIMES = 1000;
private static final ExecutorService executorService = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
Map<String, BigDecimal> complaints = new HashMap<>();
//Map<String, BigDecimal> complaints = new ConcurrentHashMap<>()
complaints.put(NAME, BigDecimal.ZERO);
// seqAdd(complaints);
concurrAdd(complaints);
executorService.shutdown();
try {
while (!executorService.awaitTermination(1, TimeUnit.SECONDS))
System.out.println(complaints);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void seqAdd(Map<String, BigDecimal> complaints) {
for (int i = 1; i <= TIMES; i++) {
addOneCompl(complaints);
}
}
//并发投诉
private static void concurrAdd(Map<String, BigDecimal> complaints) {
for (int i = 1; i <= TIMES; i++) {
executorService.submit(() -> addOneCompl(complaints));
}
}
//投诉+1
private static void addOneCompl(Map<String, BigDecimal> complaints) {
BigDecimal times = complaints.get(NAME);
if (times != null) {
complaints.put(NAME, times.add(BigDecimal.ONE));
}
}
当我们运行 seqAdd(complaints)时一切正常,输出结果{David=1000},当我们换成 concurrAdd(complaints)时,输出结果{David=589},{David=601}达不到 1000。不是有一个 ConcurrentHashMap 的类吗?看名字似乎能解决我们的并发问题?真的吗?
Map<String, BigDecimal> complaints = new ConcurrentHashMap<>()
输出结果为{David=153},比前面的情况更糟糕!
我们可以在使用 synchronized 修饰 addOneCompl()方法,但这是一个坏方法。我们可以使用 Map.computeIfPresent()修改 addOneCompl()方法
private static synchronized void addOneCompl(Map<String, BigDecimal> complaints) {
complaints.computeIfPresent(NAME, (name, times) -> times.add(BigDecimal.ONE));
}
这是一个在 java8 中存在的 api。源码是这样解释的
If the value for the specified key is present and non-null, attempts to
* compute a new mapping given the key and its current mapped value
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于