This discussion is archived
5 Replies Latest reply: Nov 4, 2012 8:05 AM by rp0428 RSS

Java HashMap sometimes returning wrong value in some threads

DavidThi808 Newbie
Currently Being Moderated
We are not using one HashMap across threads (yes that is bad for many reasons). Each thread has its own HashMap.

We have a class that extends from Thread. In Thread.run() we create a HashMap, set a key/value pair in it, and pass that HashMap to a method. That method retrieves the value from the HashMap, inserts it into a string, and returns the string.

Sometimes the returned string has a different value (still in Thread.run()). This only occurs on hardware with 3+ physical cores. And it has only happened twice (before we added logging to help us find exactly what is going on of course).

Any idea why this would occur.
  • 1. Re: Java HashMap sometimes returning wrong value in some threads
    DavidThi808 Newbie
    Currently Being Moderated
    Full code:


    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();
                   }

              }
         }
    }
  • 2. Re: Java HashMap sometimes returning wrong value in some threads
    796440 Guru
    Currently Being Moderated
    When posting code, use code tags so it will be readable: https://forums.oracle.com/forums/ann.jspa?annID=1429

    Make sure to fix the indenting, too.

    As for your problem without having read your code, and having only skimmed your explanation, I see the following possibilities:

    1) Despite your claim to the contrary, multiple threads are accessing the same Hashmap, and you're not using proper synchronization.

    2) The objects being put into the HashMap are being accessed by multiple threads, and you're not using proper syncing.

    3) Your hashCode() and or equals() method on your Map's key class is wrong.

    4) You're not actually observing what you think you are observing.
  • 3. Re: Java HashMap sometimes returning wrong value in some threads
    DrClap Expert
    Currently Being Moderated
    DavidThi808 wrote:
    We have a class that extends from Thread. In Thread.run() we create a HashMap, set a key/value pair in it, and pass that HashMap to a method. That method retrieves the value from the HashMap, inserts it into a string, and returns the string.
    If it were just two lines of code which did that, then you would have a mystery. But it isn't. At least two large and complicated classes are involved, from what I can see with a little bit of googling. It wouldn't be surprising if there was some obscure multi-threading bug in one of those classes. I don't see anything obviously wrong in your code, although it was hardly readable so I might well have missed something.

    But fortunately the classes are in a commercial product which offers support and has a page to report bugs. If you don't have the wherewithal to investigate their code (i.e. if you don't have their code) then you should certainly ask them for support.
  • 4. Re: Java HashMap sometimes returning wrong value in some threads
    DavidThi808 Newbie
    Currently Being Moderated
    Should have posted a follow-up here months ago when we got the answer.

    We were able to show that this is occurring to IBM (it is their JVM). They were able to reproduce it and reported the bug to their dev team. I believe they fixed it - it was one of our customers hitting this so at that point it was between them and IBM.
  • 5. Re: Java HashMap sometimes returning wrong value in some threads
    rp0428 Guru
    Currently Being Moderated
    >
    Any idea why this would occur.
    >
    Is it really the wrong value being returned or is it because YOU are just reporting that the wrong value is being returned?

    Correct me if I am wrong but you appear to have 100 threads all using the SAME instance of the TestThreads class and, therefor, all using the SAME instance variable hadWithVarError.

    Your class declaration is
    public class TestThreads extends TestCase 
    That class includes an instance variable
    private boolean hadWithVarError = false;
    And it includes a method that, despite the comment 'run 10 threads' creates 100 threads; the value of a static int 'NUM_THREADS = 100'.
    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);
    }
    Since that method uses 'this' it will create ALL 100 threads and reference the same instance of TestThreads.

    The 'run' method of the 'ReportThreadWithVariables' class includes this code
    if (!result.equals( expected ))
    obj.hadWithVarError = true;
    assertEquals(expected, result);
    } catch (Throwable e) {
    obj.hadWithVarError = true;
    e.printStackTrace();
    }
    
    }
    }
    }
    The 'if' statement does NOT has an opening '{' so the only statement included is
    obj.hadWithVarError = true;
    That line of code will set the value of the instance variable IN THE SAME INSTANCE USED BY THE OTHER 99 THREADS!.

    Then the 'assertEquals' will execute. Why do you have an 'if' statement that appears to test the same condition that the assert is testing?

    Any why are you sharing the same instance of TestThreads and thus the same instance variable hadWithVarError among 100 threads?

    That seems like a recipe for disaster to me.

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points