blog,blog,blog
blog,blog,blogJava Collections(集合) Bug
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},比前面的情况更糟糕!原因是ConcurrentHashMap只能保证读写操作时原子操作线程安全,但是我们的addOneCompl()方法时复合操作,相当于事务。不能保证get、set操作中间不存在其他修改值得行为。
我们可以在使用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。源码是这样解释的,属于cas类型的操作。
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