【问题标题】:HashMap order changes when using Thread but is constant without ThreadHashMap 顺序在使用 Thread 时发生变化,但在没有 Thread 时保持不变
【发布时间】:2018-09-05 11:58:10
【问题描述】:

我知道HashMap不保证顺序。考虑以下代码:

import java.util.HashMap;
import java.util.Map;

public class SandBox {
    protected static class Book {
        String name;

        public Book(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    protected static class MyThread extends Thread {
        @Override
        public void run() {
            super.run();
            final int n = 10;
            Book[] books = new Book[n];
            for (int i=0; i<n; i++)
                books[i] = new Book("b" + i);
            for (Book b : books)
                System.out.print(b + ", ");
            System.out.println();
            HashMap<Book, Object> hm = new HashMap<>();
            for (Book b : books)
                hm.put(b, null);
            for (Map.Entry<Book, Object> entry : hm.entrySet())
                System.out.print(entry.getKey() + ", ");
            System.out.println();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        t.join();
    }
}

在每次运行中,HashMap 的顺序是不同的(如预期的那样)。例如:

输出 #1:

b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, 
b3, b4, b7, b9, b0, b8, b1, b2, b6, b5,

输出#2:

b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, 
b9, b4, b3, b7, b8, b0, b1, b5, b6, b2,

但奇怪的是,如果我替换这些行

t.start();
t.join();

t.run();

(不使用多线程)输出总是一样的:

b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, 
b0, b3, b7, b4, b2, b6, b9, b1, b5, b8, 

我不明白 HashMap 的 order 和 Thread 的关系。有人可以向我解释为什么会这样吗?

【问题讨论】:

  • @ChristopheRoussy 我不认为它是重复的;但它是相关的。
  • 您可以通过将 toString 更改为 return name + " " + hashCode() % 10_000; 来使输出更有趣(使用模块使输出适合屏幕) - 您可以看到 hashCode 在单线程之间没有变化运行,它在一个线程中运行。至于为什么? shrug 使用单个线程更具确定性。但无论如何你都不应该依赖它。

标签: java multithreading hashmap


【解决方案1】:

这是因为HashMap 内部的订单将取决于哈希码的实现。

您的Book 类没有实现hashCode,因此它将使用the default one

在合理可行的情况下,hashCode 方法定义为 类 Object 确实为不同的对象返回不同的整数。 (这 通常是通过转换的内部地址来实现 对象转换成整数,但这种实现技术不是 JavaTM 编程语言所要求的。)

这意味着它将使用内存地址。

在您的情况下,对于单个线程,分配的内存地址在重新运行时是相同的,而在线程版本中情况并非如此。

但这只是“偶然”,即使在单线程中你也不能依赖它(其他人会运行它并得到不同的结果, 甚至当您稍后运行它时,您也可以获得不同的结果,因为对象将具有不同的内存地址)

在使用hashmap 中的对象时,请始终覆盖hashCode (&equals)。

【讨论】:

  • 像 IntelliJ IDEA 这样的 IDE 会为你生成 hashCode 方法,只需点击 alt-insert 并选择 hashcode。
  • “在 hashmap 中使用对象时,请始终覆盖 hashCode (&equals)。” - 这不是一个好的建议。 Object::hashCode 应该NOT如果对象需要通过标识语义相等。
  • @StephenC 是的,但是如果你想在 HashMap 中依赖标识语义,你通常应该使用java.util.IdentityHashMap,而不是强制对象不覆盖 hashCode。
  • @StephenC 我不同意 - 请参阅上面的 2 cmets - 从维护的角度和代码可读性的角度来看,我仍然会覆盖它以明确它对其他人来说是正确的行为。
  • 这意味着它将使用内存地址。 HotSpot 默认情况下不是这样。它使用随机数生成器生成身份 hashCode 并将其存储在对象头中。
【解决方案2】:

不是真正的答案,而是作为补充:这里是用于 HashMap 代码中插入(放置)的代码,TreeNode 代码:

/**
 * Tie-breaking utility for ordering insertions when equal
 * hashCodes and non-comparable. We don't require a total
 * order, just a consistent insertion rule to maintain
 * equivalence across rebalancings. Tie-breaking further than
 * necessary simplifies testing a bit.
 */
static int tieBreakOrder(Object a, Object b) {
    int d;
    if (a == null || b == null ||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}

如您所见,它依赖于原生的 System.identityHashCode

在系统中:

/**
 * Returns the same hash code for the given object as
 * would be returned by the default method hashCode(),
 * whether or not the given object's class overrides
 * hashCode().
 * The hash code for the null reference is zero.
 *
 * @param x object for which the hashCode is to be calculated
 * @return  the hashCode
 * @since   JDK1.1
 */
public static native int identityHashCode(Object x);

另请参阅此答案:How does the JVM ensure that System.identityHashCode() will never change?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-07-29
    • 2020-10-31
    • 2017-02-05
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 2020-03-22
    • 1970-01-01
    相关资源
    最近更新 更多