使用Thread时HashMap顺序会更改,但在没有Thread的情况下是常量

时间:2018-03-27 07:26:15

标签: java multithreading hashmap

我知道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的顺序与Thread之间的关系。有人可以向我解释为什么会发生这种情况吗?

2 个答案:

答案 0 :(得分:14)

这是因为内部的MATRIX(@rows, @cols, @valued) = MMULT(TRANSPOSE(SPLIT(REPT(@valued &"|",@rows),"|")),SPLIT(REPT("1|",@cols),"|")) 顺序将取决于哈希码的实现。

您的HashMap课程未实施Book因此会使用the default one

  

尽可能合理实用,由hashCode方法定义   class Object确实为不同的对象返回不同的整数。 (这个   通常通过转换内部地址来实现   将对象转换为整数,但这种实现技术不是   JavaTM编程语言所要求的。)

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

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

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

hashCode中使用对象时,请务必覆盖hashCode(&amp; equals}。

答案 1 :(得分:-1)

不是一个答案,而是作为补充:这里是用于插入(put)的代码,在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?