Extending java's ThreadLocal to allow the values to be reset across all threads

前端 未结 3 932
迷失自我
迷失自我 2021-01-23 10:19

After looking at this question, I think I want to wrap ThreadLocal to add a reset behavior.

I want to have something similar to a ThreadLocal, with a method I can call f

3条回答
  •  感动是毒
    2021-01-23 10:55

    Interacting with objects in a ThreadLocal across threads

    I'll say up front that this is a bad idea. ThreadLocal is a special class which offers speed and thread-safety benefits if used correctly. Attempting to communicate across threads with a ThreadLocal defeats the purpose of using the class in the first place.

    If you need access to an object across multiple threads there are tools designed for this purpose, notably the thread-safe collections in java.util.collect.concurrent such as ConcurrentHashMap, which you can use to replicate a ThreadLocal by using Thread objects as keys, like so:

    ConcurrentHashMap map = new ConcurrentHashMap<>();
    
    // pass map to threads, let them do work, using Thread.currentThread() as the key
    
    // Update all known thread's flags
    for(AtomicBoolean b : map.values()) {
        b.set(true);
    }
    

    Clearer, more concise, and avoids using ThreadLocal in a way it's simply not designed for.

    Notifying threads that their data is stale

    I just need a way to notify all these threads that their ThreadLocal variable is stale.

    If your goal is simply to notify other threads that something has changed you don't need a ThreadLocal at all. Simply use a single AtomicBoolean and share it with all your tasks, just like you would your ThreadLocal. As the name implies updates to an AtomicBoolean are atomic and visible cross-threads. Even better would be to use a real synchronization aid such as CyclicBarrier or Phaser, but for simple use cases there's no harm in just using an AtomicBoolean.

    Creating an updatable "ThreadLocal"

    All of that said, if you really want to implement a globally update-able ThreadLocal your implementation is broken. The fact that you haven't run into issues with it is only a coincidence and future refactoring may well introduce hard-to-diagnose bugs or crashes. That it "has worked without incident" only means your tests are incomplete.

    • First and foremost, an ArrayList is not thread-safe. You simply cannot use it (without external synchronization) when multiple threads may interact with it, even if they will do so at different times. That you aren't seeing any issues now is just a coincidence.
    • Storing the objects as a List prevents us from removing stale values. If you call ThreadLocal.set() it will append to your list without removing the previous value, which introduces both a memory leak and the potential for unexpected side-effects if you anticipated these objects becoming unreachable once the thread terminated, as is usually the case with ThreadLocal instances. Your use case avoids this issue by coincidence, but there's still no need to use a List.

    Here is an implementation of an IterableThreadLocal which safely stores and updates all existing instances of the ThreadLocal's values, and works for any type you choose to use:

    import java.util.Iterator;
    import java.util.concurrent.ConcurrentMap;
    
    import com.google.common.collect.MapMaker;
    
    /**
     * Class extends ThreadLocal to enable user to iterate over all objects
     * held by the ThreadLocal instance.  Note that this is inherently not
     * thread-safe, and violates both the contract of ThreadLocal and much
     * of the benefit of using a ThreadLocal object.  This class incurs all
     * the overhead of a ConcurrentHashMap, perhaps you would prefer to
     * simply use a ConcurrentHashMap directly instead?
     * 
     * If you do really want to use this class, be wary of its iterator.
     * While it is as threadsafe as ConcurrentHashMap's iterator, it cannot
     * guarantee that all existing objects in the ThreadLocal are available
     * to the iterator, and it cannot prevent you from doing dangerous
     * things with the returned values.  If the returned values are not
     * properly thread-safe, you will introduce issues.
     */
    public class IterableThreadLocal extends ThreadLocal
                                        implements Iterable {
        private final ConcurrentMap map;
    
        public IterableThreadLocal() {
            map = new MapMaker().weakKeys().makeMap();
        }
    
        @Override
        public T get() {
            T val = super.get();
            map.putIfAbsent(Thread.currentThread(), val);
            return val;
        }
    
        @Override
        public void set(T value) {
            map.put(Thread.currentThread(), value);
            super.set(value);
        }
    
        /**
         * Note that this method fundamentally violates the contract of
         * ThreadLocal, and exposes all objects to the calling thread.
         * Use with extreme caution, and preferably only when you know
         * no other threads will be modifying / using their ThreadLocal
         * references anymore.
         */
        @Override
        public Iterator iterator() {
            return map.values().iterator();
        }
    }
    

    As you can hopefully see this is little more than a wrapper around a ConcurrentHashMap, and incurs all the same overhead as using one directly, but hidden in the implementation of a ThreadLocal, which users generally expect to be fast and thread-safe. I implemented it for demonstration purposes, but I really cannot recommend using it in any setting.

提交回复
热议问题