鉴于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 () {}
}
答案 0 :(得分:16)
彼得说,这是不可能的。如果您想要这种功能,那么从概念上讲,您真正想要的只是一个标准Map<Thread, Long>
- 其中大多数操作将使用Thread.currentThread()
的键完成,但您可以如果你愿意,可以通过其他主题。
然而,这可能不是一个好主意。首先,持有对垂死线程的引用会使GC陷入困境,因此您必须通过额外的环节来制作密钥类型WeakReference<Thread>
。而且我不相信Thread
无论如何都是一个很棒的Map键。
因此,一旦超出了烘焙ThreadLocal
的便利性,或许值得质疑是否使用Thread
对象作为关键是最佳选择?为每个线程提供唯一ID(字符串或整数,如果它们还没有更有意义的自然键)可能更好,只需使用这些来关闭地图。我意识到你的例子是人为的,但你可以用Map<String, Long>
并使用"t1"
和"t2"
的键来做同样的事情。
由于Map
表示您实际使用数据结构的方式,因此也可以说更清楚; ThreadLocals更像是标量变量,具有一些访问控制魔法而不是集合,因此即使可以根据需要使用它们,对于查看代码的其他人来说也可能会更加混乱。
答案 1 :(得分:9)
基于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"
之前的所有评论仍然适用 - 不安全!
但是出于调试目的,它可能正是你所需要的 - 我就这样使用它。
答案 2 :(得分:6)
我想看看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);
}
}
答案 3 :(得分:5)
只有将相同的值放在不是ThreadLocal的字段中才能进行访问。根据定义,ThreadLocal只对该线程是本地的。
答案 4 :(得分:5)
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
确保在访问字段时不进行展览数据竞赛。所以你可能需要volatile,synchronized等等。
整体地图是一个特里巴德的想法,永远不会保留对你没有明确管理/拥有的对象的引用;特别是当谈到Thread,ThreadGroup,Class,ClassLoader ...... WeakHashMap<Thread, Object>
稍微好一些,但是你需要专门访问它(即在锁定下),这可能会阻碍重度多线程环境中的性能。 WeakHashMap不是世界上最快的东西。
ConcurrentMap,Object&gt;会更好,但你需要一个有equals
和hashCode
...