【问题标题】:How to demonstrate a race condition in a HashMap?如何在 HashMap 中演示竞争条件?
【发布时间】:2017-12-01 03:46:59
【问题描述】:

我正在教授 Java 并发编程,并想向学生展示使用非线程安全数据结构可能出现的问题。我创建了以下程序:

    Map<String,String> favoriteFoods = new HashMap<>();
    Thread t1 = new Thread(() -> {
        favoriteFoods.put("Alice","avocado");
        favoriteFoods.put("Bob","banana");
    });
    Thread t2 = new Thread(() -> {
        favoriteFoods.put("Alice","aloysia");
        favoriteFoods.put("Carl","candy");
    });
    t1.start();
    t2.start();

    t1.join();
    t2.join();
    System.out.println(favoriteFoods);

从两个不同的线程访问非线程安全的 HashMap。但是,该程序每次运行时都能正常运行。

如何更改代码以演示问题?

【问题讨论】:

  • 病态地思考。在您的匿名Thread 实例中放置循环,并删除joins 中的一个或两个...
  • 这确实是一个Computer Science Educators 问题,可能应该迁移到那里。此外,听起来您可能对那个网站感兴趣:)

标签: java concurrency thread-safety


【解决方案1】:

尝试向 HashMap 中添加哈希冲突的元素:

import java.util.*;
public class HashMapRaceCondition2 {
    public static void main(String[] args) throws Exception {
        class MyClass {
            int value;

            MyClass(int value) {
                this.value = value;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;

                MyClass myClass = (MyClass) o;

                if (value != myClass.value) return false;

                return true;
            }

            @Override
            public int hashCode() {
                return 42;
            }

            @Override
            public String toString() {
                return "MyClass{" +
                        "value=" + value +
                        '}';
            }
        }  // MyClass

        Map<MyClass,Integer> map = new HashMap<>();

        Thread t1 = new Thread(() -> {
            for (int i =0; i < 1000; ++i) {
                map .put(new MyClass(i), i);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 2000; i < 3000; ++i) {
                map.put(new MyClass(i), i);
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(map.size());
    }
}

此代码在我的机器上每执行 5-6 次就会失败一次。

【讨论】:

  • 以什么方式失败?
  • 酷!我在每个线程中添加 1000 个项目,几乎每次运行,项目总数都少于 2000 个!正如预期的那样,当我使用 ConcurrentHashMap 时,它变成了 2000。
  • 事实上,该演示甚至可以使用普通的 Map
【解决方案2】:

例如:

Map<Integer, Integer> map = new HashMap<>(1);

    IntStream.range(0, 100)
            .parallel()
            .map(x -> {
                map.put(x, x);
                return x;
            })
            .max();

    System.out.println(map.size());

运行几次,结果不会一直是100

【讨论】:

    【解决方案3】:

    恕我直言,您的第一个代码应该在没有任何进一步更改的情况下演示竞争条件。我试了好几次,有时爱丽丝“是”鳄梨,有时爱丽丝“是”阿洛西娅。我假设您所谓的正确答案(在这种情况下 Alisia 仅映射到鳄梨或仅映射到 aloysia)是由于超快的多核计算机。您可以询问您的学生中是否有人可以在一些较旧的硬件上运行您的代码。

    【讨论】:

    • 该代码演示了竞态条件,但并未演示不安全性。有趣的案例 - 真正证明 synchronizedConcurrentHashMap 等等的案例 - 是映射完全消失的案例,甚至是 HashMap 完全损坏的案例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-12
    • 1970-01-01
    • 1970-01-01
    • 2019-03-02
    • 1970-01-01
    • 2022-01-23
    相关资源
    最近更新 更多