【问题标题】:ThreadLocal value access across different threads跨不同线程的 ThreadLocal 值访问
【发布时间】:2011-07-07 23:56:29
【问题描述】:

鉴于一个 ThreadLocal 变量为不同的线程保存不同的值,是否可以从另一个线程访问一个 ThreadLocal 变量的值?

即在下面的示例代码中,是否可以在 t1 中从 t2 读取 TLocWrapper.tlint 的值?

public class Example
{
  public static void main (String[] args)
  {
    Tex t1 = new Tex("t1"), t2 = new Tex("t2");
    new Thread(t1).start();
    try
    {
      Thread.sleep(100);
    }
    catch (InterruptedException e)
    {}
    new Thread(t2).start();
    try
    {
      Thread.sleep(1000);
    }
    catch (InterruptedException e)
    {}
    t1.kill = true;
    t2.kill = true;
  }

  private static class Tex implements Runnable
  {
    final String name;
    Tex (String name)
    {
      this.name = name;
    }
    public boolean kill = false;
    public void run ()
    {
      TLocWrapper.get().tlint.set(System.currentTimeMillis());
      while (!kill)
      {
        // read value of tlint from TLocWrapper
        System.out.println(name + ": " + TLocWrapper.get().tlint.get());
      }
    }
  }
}
class TLocWrapper
{
  public ThreadLocal<Long> tlint = new ThreadLocal<Long>();
  static final TLocWrapper self = new TLocWrapper();
  static TLocWrapper get ()
  {
    return self;
  }
  private TLocWrapper () {}
}

【问题讨论】:

  • 具有适当读/写锁定的常规变量用于在线程之间共享数据。 ThreadLocal 是专门在您/不/想要在线程之间共享数据的情况下创建的。这让我相信这要么是一个纯粹的假设性问题,要么你正试图用 ThreadLocal 做一些事情,它是专门不打算用于的。
  • @Cthulhu:是的,我正在尝试用 ThreadLocal 做一些“邪恶”的事情——但我的意图是好的 :)。我只是想解决一个问题。
  • 顺便说一句:你必须让 kill 字段易变,这是一个非常微妙的错误。

标签: java multithreading


【解决方案1】:

正如彼得所说,这是不可能的。如果您想要这种功能,那么从概念上讲,您真正想要的只是一个标准的Map&lt;Thread, Long&gt; - 其中大多数 操作将使用Thread.currentThread() 的键完成,但您可以传入其他线程如果你愿意。

但是,这可能不是一个好主意。一方面,持有对垂死线程的引用会弄乱 GC,因此您必须通过额外的循环来创建密钥类型 WeakReference&lt;Thread&gt;。而且我不相信Thread 是一个很棒的 Map 键。

因此,一旦您超越了内置 ThreadLocal 的便利性,也许值得质疑使用 Thread 对象作为键是否是最佳选择?最好为每个线程提供唯一的 ID(字符串或整数,如果它们还没有更有意义的自然键),然后简单地使用 这些 键关闭映射。我意识到您的示例是人为的,但是您可以使用 Map&lt;String, Long&gt; 并使用 "t1""t2" 的键来做同样的事情。

因为Map 代表您实际使用数据结构的方式,所以它也可以说更清楚; ThreadLocals 更像是一个带有一点访问控制魔法的标量变量,而不是一个集合,所以即使你可以随意使用它们,它也可能会让其他查看你的代码的人更加困惑。

【讨论】:

  • 为什么 Thread 不是一个好的 Map 键?并且使用 Map 真的更好吗?当线程终止时,您将泄漏地图中的数据。即使使用 Map,V>,您也不会泄漏线程,但会泄漏线程本地数据。我建议,创建一个持有者对象来持有 V,然后将持有者存储两次:一次在普通的 ThreadLocal 中,然后再次在由 Thread 索引的同步或并发映射中,以供其他线程访问。然后创建一个更清洁的守护线程,定期从映射中删除死线程。
【解决方案2】:

根据 Andrzej Doyle 的回答,这里有一个完整的工作解决方案:

ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("Test"); // do this in otherThread

Thread otherThread = Thread.currentThread(); // get a reference to the otherThread somehow (this is just for demo)

Field field = Thread.class.getDeclaredField("threadLocals");
field.setAccessible(true);
Object map = field.get(otherThread);

Method method = Class.forName("java.lang.ThreadLocal$ThreadLocalMap").getDeclaredMethod("getEntry", ThreadLocal.class);
method.setAccessible(true);
WeakReference entry = (WeakReference) method.invoke(map, threadLocal);

Field valueField = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry").getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(entry);

System.out.println("value: " + value); // prints: "value: Test"

当然,所有以前的 cmets 仍然适用 - 不安全

但出于调试目的,它可能正是您所需要的——我就是这样使用的。

【讨论】:

    【解决方案3】:

    我想看看 ThreadLocal 存储中有什么,所以我扩展了上面的例子来展示给我看。也方便调试。

                Field field = Thread.class.getDeclaredField("threadLocals");
                field.setAccessible(true);
                Object map = field.get(Thread.currentThread());
                Field table = Class.forName("java.lang.ThreadLocal$ThreadLocalMap").getDeclaredField("table");
                table.setAccessible(true);
                Object tbl = table.get(map);
                int length = Array.getLength(tbl);
                for(int i = 0; i < length; i++) {                   
                    Object entry = Array.get(tbl, i);
                    Object value = null;
                    String valueClass = null;
                    if(entry != null) { 
                        Field valueField = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry").getDeclaredField("value");
                        valueField.setAccessible(true);
                        value = valueField.get(entry);
                        if(value != null) {
                            valueClass = value.getClass().getName();
                        }
                        Logger.getRootLogger().info("[" + i + "] type[" + valueClass + "] " + value);
                    }
                }
    

    【讨论】:

    • 不错的一款专门用于调试的。
    【解决方案4】:

    只有在非 ThreadLocal 的字段中放置相同的值并改为访问该字段时才有可能。根据定义,ThreadLocal 仅对该线程是本地的。

    【讨论】:

      【解决方案5】:

      ThreadLocalMap可以通过反射和Thread.class.getDeclaredField("threadLocals")setAccssible(true)等方式访问。 但是,不要那样做。该映射预计只能由拥有的线程访问,访问 ThreadLocal 的任何值都是潜在的数据竞争。

      但是,如果您可以忍受上述数据竞赛,或者只是避免它们(更好的主意)。这是最简单的解决方案。扩展 Thread 并在那里定义你需要的任何东西,就是这样:

      ThreadX extends Thread{
        int extraField1;
        String blah2; //and so on
      }
      

      这是一个不错的解决方案,它不依赖于 WeakReferences,但需要您创建线程。你可以这样设置((ThreadX)Thread.currentThread()).extraField1=22

      确保您在访问字段时没有展示数据竞争。所以你可能需要volatile、synchronized等等。

      Overall Map 是一个非常糟糕的想法,永远不要保留对您未明确管理/拥有的对象的引用;特别是当涉及到 Thread、ThreadGroup、Class、ClassLoader...WeakHashMap&lt;Thread, Object&gt; 稍好一些,但是您需要以独占方式访问它(即处于锁定状态),这可能会降低在大量多线程环境中的性能。 WeakHashMap 并不是世界上最快的东西。

      ConcurrentMap, Object> 会更好,但您需要一个 WeakRef 具有 equalshashCode...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多