【问题标题】:Java HashMap sometimes returning wrong value in some threadsJava HashMap 有时会在某些线程中返回错误的值
【发布时间】:2012-02-22 22:22:05
【问题描述】:

更新:这是一个深埋在某些代码中的静态,它仅用于几条指令。谢谢大家的建议。

我们没有跨线程使用一个 HashMap(是的,这很糟糕,原因有很多)。每个线程都有自己的 HashMap。

我们有一个从 Thread 扩展而来的类。在 Thread.run() 中,我们创建一个 HashMap,在其中设置一个键/值对,然后将该 HashMap 传递给一个方法。该方法从 HashMap 中检索值,将其插入到字符串中,然后返回该字符串。

有时返回的字符串有不同的值(仍在 Thread.run() 中)。这只发生在具有 3 个以上物理内核的硬件上。而且它只发生过两次(当然,在我们添加日志记录以帮助我们准确了解发生了什么之前)。

知道为什么会发生这种情况。

更新:这是完整的代码。 ProcessTxt 是从 HashMap 中提取值并将其放入字符串中。

import java.io.*;
import java.util.HashMap;

import junit.framework.TestCase;
import net.windward.datasource.dom4j.Dom4jDataSource;
import net.windward.xmlreport.ProcessReport;
import net.windward.xmlreport.ProcessTxt;

/**
 * Test calling from multiple threads
 */
public class TestThreads extends TestCase {

    private static String path = ".";

    // JUnit stuff
    public TestThreads(String name) {
        super(name);
    }

    // Get logging going - called before any tests run
    protected void setUp() throws Exception {
        ProcessReport.init();
    }

    // this is not necessary - called after any tests are run
    protected void tearDown() {
    }

    private static final int NUM_THREADS = 100;

    private boolean hadWithVarError = false;


    /**
     * Test that each thread has unique variables.
     */
    public void testRunReportsWithVariables() throws Exception {

        // run 10 threads
        ReportThreadWithVariables[] th = new ReportThreadWithVariables[NUM_THREADS];
        for (int ind = 0; ind < NUM_THREADS; ind++) {
            th[ind] = new ReportThreadWithVariables(this, ind);
            th[ind].setName("Run " + ind);
        }
        for (int ind = 0; ind < NUM_THREADS; ind++)
            th[ind].start();

        boolean allDone = false;
        while (!allDone) {
            Thread.sleep(100);
            allDone = true;
            for (int ind = 0; ind < NUM_THREADS; ind++)
                if (th[ind].isAlive())
                    allDone = false;
        }

        assertTrue(!hadWithVarError);
    }

    public static class ReportThreadWithVariables extends Thread {

        private TestThreads obj;
        private int num;

        public ReportThreadWithVariables(TestThreads tt, int num) {
            obj = tt;
            this.num = num;
        }

        public void run() {

            try{
                System.out.println("starting " + num);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ProcessTxt pt = new ProcessTxt(new FileInputStream(new File(path, "Thread_Test.docx")), out);

                pt.processSetup();

                // don't use order1.xml, but need a datasource.
                Dom4jDataSource datasource = new Dom4jDataSource(new FileInputStream(new File(path, "order1.xml")));
                HashMap map = new HashMap();
                map.put("num", new Integer(num));
                datasource.setMap(map);
                pt.processData(datasource, "");

                pt.processComplete();
                String result = out.toString().trim();
                System.out.println("complete " + num + ", result = " + result);

                String expected = "Number: " + num;
                if (!result.equals( expected ))
                    obj.hadWithVarError = true;
                assertEquals(expected, result);
            } catch (Throwable e) {
                obj.hadWithVarError = true;
                e.printStackTrace();
            }

        }
    }
}

(编辑格式代码)

【问题讨论】:

  • 关键是什么?一个简短但完整的程序来演示这个问题会真的帮助...
  • 值的类型也可能是相关的。这些值是否可能包含一个在其他地方被引用并随后被修改的对象?
  • 您发布的代码写得不太好。您可能会发现进行常规代码清理可以解决您的问题。例如,即使您对 hadWithVarError 的处理也很激烈......它“听起来应该可以工作”,但由于指定 Java 内存模型的方式,您必须正确同步(或标记 volatile)甚至是简单的布尔字段。
  • 除非我遗漏了什么单元测试是错误的。您永远不会写入 ByteArrayOutputStream 'out',而是从中读取 'result'

标签: java hashmap


【解决方案1】:

鉴于缺少代码并且仅基于已编写的内容,我将假设某些东西是static。也就是说,在某处,静态成员被存储到/写入。

【讨论】:

  • 这是我们寻找的第一件事——没有什么是静态的。此外,这种情况很少发生——我们必须在 4 核系统上运行大约 4 分钟才能得到它。在 2 核系统上,它永远不会发生。我会发布代码。
【解决方案2】:

num 不是可变的,并且其他变量(字符串、映射)是本地的,因此 ReportThreadWithVariables 看起来是线程安全的。在我看来,问题在于对外部对象的调用,而不是您发布的内容。 您使用的类是否记录为线程安全?

例如,javadoc of the processData method 声明不应为您似乎正在执行的同一数据源(相同的文件名)多次调用它。

ps:(不相关)您可以使用 CountDownLatch 代替 while 循环。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-21
    • 1970-01-01
    • 2016-03-01
    相关资源
    最近更新 更多