United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-5025230 (thread) Creating thread local variables from within ThreadLocal.initialValue()
JDK-5025230 : (thread) Creating thread local variables from within ThreadLocal.initialValue()

Details
Type:
Enhancement
Submit Date:
2004-04-01
Status:
Resolved
Updated Date:
2005-09-30
Project Name:
JDK
Resolved Date:
2005-09-30
Component:
core-libs
OS:
generic,windows_2000
Sub-Component:
java.lang
CPU:
x86,generic
Priority:
P3
Resolution:
Fixed
Affected Versions:
1.4.2,6
Fixed Versions:

Related Reports
Relates:
Relates:

Sub Tasks

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
                                     
2005-04-26
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
                                     
2005-04-26



Hardware and Software, Engineered to Work Together