Java Collections(集合) Bug

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},比前面的情况更糟糕!原因是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