JDK-5025230 : (thread) Creating thread local variables from within ThreadLocal.initialValue()
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.4.2,6
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,windows_2000
  • CPU: generic,x86
  • Submitted: 2004-04-01
  • Updated: 2005-09-30
  • Resolved: 2005-09-30
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6
6 b55Fixed
Related Reports
Relates :  
Relates :  
Description
Name: rmT116609			Date: 04/01/2004


A DESCRIPTION OF THE REQUEST :
Provide a guaranteed method of creating thread local variables from within ThreadLocal.initialValue().

JUSTIFICATION :
Currently thread local variables created from within ThreadLocal.initialValue() are not guaranteed to live after the ThreadLocal.get() method completes. That is because get() creates a new ThreadLocalMap possibly overwriting any thread local variables that may have been created from within initialValue().

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
    public Object get()
    {
        Thread t = Thread.currentThread();
        
        ThreadLocalMap map = getMap(t);

        if (map != null)
            return map.get(this);

        Object value = initialValue();

        ThreadLocalMap map = getMap(t);

        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);

        return value;
    }

ACTUAL -
    public Object get()
    {
        Thread t = Thread.currentThread();
        
        ThreadLocalMap map = getMap(t);

        if (map != null)
            return map.get(this);

        Object value = initialValue();

        createMap(t, value);

        return value;
    }


---------- BEGIN SOURCE ----------
JUnit / relection uses ThreadLocal I believe, so I had to resort to a POJO test.

package junit;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author Harish Krishnaswamy
 * @version $Id: $
 * @since 1.0
 */
public class TestThreadLocal
{
    private static class TestThreadEventNotifier
    {
        private ThreadLocal _local = new ThreadLocal();

        private List getListeners()
        {
            List list = (List) _local.get();

            if (list == null)
            {
                list = new ArrayList();
                _local.set(list);
            }

            return list;
        }

        public void addListener(TestThreadLocalStorage listener)
        {
            List list = getListeners();

            list.add(listener);
        }

        public void fireThreadCleanup()
        {
            List list = getListeners();

            for (Iterator itr = list.iterator(); itr.hasNext();)
            {
                TestThreadLocalStorage listener = (TestThreadLocalStorage) itr.next();

                listener.cleanup();
            }
        }
    }

    private static class TestThreadLocalStorage
    {
        private CleanableThreadLocal _local;

        TestThreadLocalStorage(TestThreadEventNotifier notifier)
        {
            _local = new CleanableThreadLocal(this, notifier);
        }

        private class CleanableThreadLocal extends ThreadLocal
        {
            private TestThreadLocalStorage  _storage;
            private TestThreadEventNotifier _notifier;

            private CleanableThreadLocal(TestThreadLocalStorage storage,
                TestThreadEventNotifier notifier)
            {
                _storage = storage;
                _notifier = notifier;
            }

            protected Object initialValue()
            {
                if (_notifier != null && _storage != null)
                    _notifier.addListener(_storage);

                return new HashMap();
            }
        }

        public Object get(Object key)
        {
            Map map = (Map) _local.get();

            return map.get(key);
        }

        public void put(Object key, Object value)
        {
            Map map = (Map) _local.get();

            map.put(key, value);
        }

        public void cleanup()
        {
            Map map = (Map) _local.get();

            map.clear();
        }

    }

    public static void main(String[] args)
    {
        TestThreadEventNotifier notifier = new TestThreadEventNotifier();
        TestThreadLocalStorage storage = new TestThreadLocalStorage(notifier);

        storage.put("mykey", "myvalue");

        notifier.fireThreadCleanup();

        Object myVal = storage.get("mykey");
        
        if (myVal == null)
            System.out.println("ThreadLocal values successfully cleared!");
        else
            System.out.println("ERROR: ThreadLocal values not cleared: " + myVal);
    }
}

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

CUSTOMER SUBMITTED WORKAROUND :
package junit;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author Harish Krishnaswamy
 * @version $Id: $
 * @since 1.0
 */
public class TestThreadLocalWorkaround
{
    private static class TestThreadEventNotifier
    {
        private ThreadLocal _local = new ThreadLocal();

        private List getListeners()
        {
            List list = (List) _local.get();

            if (list == null)
            {
                list = new ArrayList();
                _local.set(list);
            }

            return list;
        }

        public void addListener(ThreadLocalStorage listener)
        {
            List list = getListeners();

            list.add(listener);
        }

        public void fireThreadCleanup()
        {
            List list = getListeners();

            for (Iterator itr = list.iterator(); itr.hasNext();)
            {
                ThreadLocalStorage listener = (ThreadLocalStorage) itr.next();

                listener.cleanup();
            }
        }
    }

    private static class ThreadLocalStorage
    {
        private static final String     INITIALIZED_KEY = "$init$";

        private CleanableThreadLocal    _local          = new CleanableThreadLocal();
        private TestThreadEventNotifier _notifier;

        ThreadLocalStorage(TestThreadEventNotifier notifier)
        {
            _notifier = notifier;
        }

        private class CleanableThreadLocal extends ThreadLocal
        {
            protected Object initialValue()
            {
                Map map = new HashMap();
                map.put(INITIALIZED_KEY, Boolean.TRUE);

                return map;
            }
        }

        private Map getThreadLocalVariable()
        {
            Map map = (Map) _local.get();

            if (Boolean.TRUE.equals(map.get(INITIALIZED_KEY)) && _notifier != null)
            {
                _notifier.addListener(this);

                map.remove(INITIALIZED_KEY);
            }

            return map;
        }

        public Object get(Object key)
        {
            Map map = getThreadLocalVariable();

            return map.get(key);
        }

        public void put(Object key, Object value)
        {
            Map map = getThreadLocalVariable();

            map.put(key, value);
        }

        public void cleanup()
        {
            Map map = (Map) _local.get();

            map.clear();
        }

    }

    public static void main(String[] args)
    {
        TestThreadEventNotifier notifier = new TestThreadEventNotifier();
        ThreadLocalStorage storage = new ThreadLocalStorage(notifier);

        storage.put("mykey", "myvalue");

        notifier.fireThreadCleanup();

        Object myVal = storage.get("mykey");
        
        if (myVal == null)
            System.out.println("ThreadLocal values successfully cleared!");
        else
            System.out.println("ERROR: ThreadLocal values not cleared: " + myVal);
    }
}
(Incident Review ID: 241038) 
======================================================================

Comments
EVALUATION This RFE will deliver useability of the API in certain recursive settings in return for a relatively simple change that is performance neutral and results in slightly simpler code. ###@###.### 2005-04-26 19:01:20 GMT
26-04-2005

SUGGESTED FIX One of the original ThreadLocal authors has submitted a suggested implementation and this has passed some tests already. ###@###.### 2005-04-26 19:01:20 GMT
26-04-2005