Java Collections(集合) Bug

本贴最后更新于 2168 天前,其中的信息可能已经事过境迁

Java Collections(集合) Bug


maxresdefaultjpg


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
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3453 回帖 • 203 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...