JDK-6550283 : (thread) ThreadLocal.initialValue() may be called multiple times in some cases
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: linux
  • CPU: x86
  • Submitted: 2007-04-25
  • Updated: 2010-04-04
  • Resolved: 2007-06-08
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.4.2-03"
Java(TM) 2 Runtime Environment, Standard Edition (build Blackdown-1.4.2-03)
Java HotSpot(TM) Client VM (build Blackdown-1.4.2-03, mixed mode)
adam@gradall:~/code/webslinger-fix/trunk/bug$ /usr/lib/j2sdk1.5-sun/bin/java -version
java version "1.5.0_11"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03)
Java HotSpot(TM) Client VM (build 1.5.0_11-b03, mixed mode, sharing)


ADDITIONAL OS VERSION INFORMATION :
Linux gradall 2.6.20.1 #1 PREEMPT Sun Feb 25 11:44:44 CST 2007 i686 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
In ThreadLocalMap, when there is a miss, and it calls initialValue, if, during that call, additional ThreadLocals are allocated, it could cause the 'map' to resize.  When the initial getAfterMiss resumes, the index that it stores the Entry into is no longer valid, as the table size has changed.

The bug does not occur on java 1.6, because this class was rewritten, to only have the top-level get() set the value if not found, instead of inside getAfterMiss().

The following patch fixes it:
==
--- /tmp/ThreadLocal-1.5.java   2005-06-03 02:17:28.000000000 -0500
+++ ThreadLocal.java    2007-03-02 14:59:59.000000000 -0600
@@ -372,11 +372,7 @@
             }
 
             Object value = key.initialValue();
-            tab[i] = new Entry(key, value);
-            int sz = ++size;
-            if (!cleanSomeSlots(i, sz) && sz >= threshold)
-                rehash();
-
+            set(key, value);
             return value;
         }
 
==


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1: javac ThreadLocalBug
2: java -classpath . ThreadLocalBug


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect to see 'initialValue called' printed one, and first/second/third printed with the same list of values.
ACTUAL -
'initialValue called' printed twice, with second/third having the same value, while first is different.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.ArrayList;

public class ThreadLocalBug {
    static int COUNT = 8;

    static ThreadLocal tl = new ThreadLocal() {
        protected Object initialValue() {
            System.err.println("initialValue called");
            ArrayList list = new ArrayList(COUNT);
            for (int i = 0; i < COUNT; i++) {
                MyThreadLocal mtl = new MyThreadLocal();
                mtl.get();
                list.add(mtl);
            }
            return list;
        }
    };

    public static void main(String[] args) throws Throwable {
        Object first = tl.get();
        Object second = tl.get();
        Object third = tl.get();
        System.err.println("first=" + first);
        System.err.println("second=" + second);
        System.err.println("second=" + third);
    }

    static class MyThreadLocal extends ThreadLocal {
        protected Object initialValue() {
            return Boolean.TRUE;
        }
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use ThreadLocal.get() == null, then ThreadLocal.set(object).

Comments
EVALUATION Given that this is fixed in jdk6, and considering available resources, and the lack of SDN votes or customer escalations, it seems best to not backport this fix to jdk5. Closing as Will Not Fix.
08-06-2007

WORK AROUND Guard initialValue calls so they cannot be concurrent.
10-05-2007

EVALUATION This defect is reproducible and is a valid concern for users of SE versions earlier than 6 where concurrent invocations of initialValue are possible. The workaround is to properly guard the method calls so they are forced to be serial.
10-05-2007