在多个线程下显示非并发Map断开

时间:2016-04-09 17:18:11

标签: multithreading java-8

我正在尝试演示使用具有多个并发任务的普通Map的问题。以下示例(编译和运行)旨在显示Map失败:

import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;

class BreakMap2 implements Runnable {
  private Map<Integer, Integer> map;
  public BreakMap2(Map<Integer, Integer> map) {
    this.map = map;
  }
  @Override
  public void run() {
    while(true) {
      int key = ThreadLocalRandom.current().nextInt(10_000);
      if(map.containsKey(key)) {
        assert map.get(key) == key;
      }
      map.put(key, key);
    }
  }
}

public class MapBreaker2 {
  public static void main(String[] args) {
    Map<Integer, Integer> map = new HashMap<>();
    IntStream.range(0, 1000)
      .mapToObj(i -> new BreakMap2(map))
      .map(CompletableFuture::runAsync)
      .collect(Collectors.toList())
      .forEach(CompletableFuture::join);
  }
}

这不能证明问题(它不会失败)。我怎样才能更有效地做到这一点?是否存在快速可靠的失败方法?

为了澄清,我试图说明如何将多个任务写入Map并非设计用于并发使用是不安全的。我正在尝试创建一些因为并发访问而向Map显示错误写入的内容。

编辑:我已经简化了示例,所以它现在只运行直到你点击Control-C。我想要的是没有停止该计划。

1 个答案:

答案 0 :(得分:1)

目前尚不清楚你试图挑起哪种“不正确的写作”。

数据结构的并发更新可能会出现各种各样的问题。 HashMap的非同步并发更新的一个臭名昭着的问题,确实出现在实际应用程序中,HashMap.get被卡在无限循环中,当然,无论如何无论如何运行的程序都无法实现发现它。

您唯一要测试的是存储的Integerassert map.get(key) == key;的值。这不会测试对象标识(否则由于未指定的自动对象标识而注定要失败)盒装值),而是包含int值,它可以从final字段的保证中受益,即使它们的所有者对象是通过数据竞争发布的。因此,您无法在此处看到未初始化的值,并且很难想象任何可能遇到错误值的情况。

由于您存储的值不受数据争用影响的不可变对象,因此只有Map本身的结构更新才会生效。但这些很少见。您使用Map0之间的随机密钥在无限循环中填充10000,因此一旦遇到该范围内的所有一万个不同的密钥,就不会再发生结构变化。机会很好,即使在下一个任务开始工作之前,第一个异步任务也会达到该状态。即使存在短暂的重叠阶段,在该时间窗口中遇到数据竞争的可能性也很低。

在那个短时间窗口之后,你只是替换现有映射的值,如上所述,对象不受数据竞争影响,并且由于它们代表相同的盒装值,JVM甚至可以优化整个更新。

如果您想要一个失败可能性较高的程序,可以尝试以下操作。它执行从一个Map到另一个public class MapBreaker2 { public static void main(String[] args) throws InterruptedException { int threadCount = 2; // try varying that number Map<Integer, Integer> source = IntStream.range(0, 10_000) .boxed().collect(Collectors.toMap(i->i, i->i)); System.out.println("trying to copy "+source.size()+" mappings without synchonizing"); Map<Integer, Integer> target = new HashMap<>(); Callable<?> job=() -> { while(!source.isEmpty()) { int key = ThreadLocalRandom.current().nextInt(10_000); Integer value=source.remove(key); if(value!=null) target.put(key, value); } return null; }; ExecutorService pool = Executors.newCachedThreadPool(); pool.invokeAll(Collections.nCopies(threadCount, job)); pool.shutdown(); System.out.println(target.size()); assert source.isEmpty(); assert target.size()==10_000; } } 的简单天真转移,探测条目并将其放入目标地图(如果存在)。它看起来很简单,并且确实可以在线程数为1的情况下顺利运行,但在使用任何其他线程数时,在大多数环境中都会出现严重故障。

session.setAttribute("j_password", password);

但应该强调的是,没有同步的多线程仍然是不可预测的,因此它可能在一个或另一个测试运行中没有明显错误的情况下运行...