Подтвердить что ты не робот

Доступ к значениям ThreadLocal для разных потоков

Учитывая, что переменная ThreadLocal содержит разные значения для разных потоков, возможно ли получить доступ к значению одной переменной ThreadLocal из другого потока?

т.е. в приведенном ниже примере, возможно ли в t1 прочитать значение TLocWrapper.tlint из t2?

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 () {}
}
4b9b3361

Ответ 1

Как говорит Питер, это невозможно. Если вам нужна такая функциональность, то концептуально то, что вы действительно хотите, - это просто стандартный Map<Thread, Long> - где большинство операций будет выполняться с помощью ключа Thread.currentThread(), но вы можете передать другие потоки, если хотите.

Однако, это, вероятно, не очень хорошая идея. Во-первых, сохранение ссылки на умирающие потоки будет испорчено GC, поэтому вам придется пройти через дополнительный обруч для создания ключевого типа WeakReference<Thread>. И я не уверен, что Thread - это отличный ключ карты.

Итак, как только вы выходите за пределы удобства запеченного ThreadLocal, возможно, стоит поставить вопрос о том, является ли использование объекта Thread в качестве ключа лучшим вариантом? Лучше дать каждому потоку уникальные идентификаторы (строки или ints, если у них еще нет естественных ключей, которые имеют больше смысла), и просто используйте их, чтобы отключить карту. Я понимаю, что ваш пример надуман, но вы можете сделать то же самое с Map<String, Long> и использовать клавиши "t1" и "t2".

Это также возможно было бы более ясным, поскольку Map представляет, как вы на самом деле используете структуру данных; ThreadLocals больше похожи на скалярные переменные с немного магии контроля доступа, чем на коллекцию, поэтому даже если бы их можно было использовать, как вы хотите, это, скорее всего, будет более запутанным для других людей, смотрящих на ваш код.

Ответ 2

Основываясь на ответе Анджей Дойла на полное рабочее решение:

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"

Все предыдущие комментарии по-прежнему применяются, конечно, не безопасно!

Но для целей отладки это может быть именно то, что вам нужно - я использую его таким образом.

Ответ 3

Это возможно только в том случае, если вы поместите одно и то же значение в поле, которое не является ThreadLocal, и получите доступ к нему. По определению ThreadLocal является только локальным для этого потока.

Ответ 4

ThreadLocalMap МОЖЕТ быть доступен через Reflection и Thread.class.getDeclaredField("threadLocals") setAccssible(true) и т.д. Не делай этого. Ожидается, что к карте будет доступен только принадлежащий поток, и доступ к любому значению ThreadLocal - это потенциальная гонка данных.

Однако, если вы можете жить с указанными гонками данных или просто избегать их (лучше понять идею). Вот простейшее решение. Расширьте поток и определите все, что вам нужно, чтобы он:

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

Это достойное решение, которое не зависит от WeakReferences, но требует создания потоков. Вы можете установить такой ((ThreadX)Thread.currentThread()).extraField1=22

Удостоверьтесь, что вы не выполняете никаких показов данных при доступе к полям. Таким образом, вам может потребоваться изменчивость, синхронизация и т.д.

Общая карта - идея terribad, никогда не держите ссылки на объект, который вы не управляете/не владеете явно; особенно когда речь заходит о Thread, ThreadGroup, Class, ClassLoader... WeakHashMap<Thread, Object> немного лучше, однако вам нужно получить доступ к ней исключительно (т.е. под блокировкой), что может снизить производительность в многопоточной среде. WeakHashMap - это не самая быстрая вещь в мире.

ConcurrentMap, Object > будет лучше, но вам нужен WeakRef с equals и hashCode...

Ответ 5

Я хотел посмотреть, что было в хранилище 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);
                }
            }