Java的Threadlocal是否可以应用于非静态字段,如果是,如何?

时间:2015-05-08 23:28:47

标签: java multithreading

ThreadLocal确保字段是全局的并且是线程的本地字段。 (全局因为它可用于线程和本地中的所有方法,因为它仅限于该线程的堆栈。)

这对我来说没什么意义,因为每个线程的堆栈仅限于该线程。所以它已经是'threadlocal',对吧?

为什么我们需要ThreadLocal? - 在进一步阅读时,我确认了我从各个站点(其中大多数未能提供这些事实或相互矛盾)的假设,这确实适用于静态字段。 哪个有意义。

所以我的问题是,是否有一个多线程场景,ThreadLocal可以/需要应用于非静态字段? (我发现一些网站说“ThreadLocal”是“主要”用于静态字段;甚至https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html使用“通常”这个词

4 个答案:

答案 0 :(得分:6)

我认为这个问题是基于错误的前提。

  

ThreadLocal确保字段是全局的并且是线程的本地字段。 (全局因为它可用于线程和本地中的所有方法,因为它仅限于该线程的堆栈。)

这不是“全球”的真正含义。全球真的意味着整个计划无法获得任何资格。实际上,Java没有真正的全局变量。它最接近的是“公共静态”字段......可以通过限定访问。

但回到问题......

如果满足两个条件,则方法可以使用threadlocal 变量

  • 方法调用必须位于正确的线程上。 (如果它位于不同的线程上,则会看到不同的变量。)

  • 该方法必须能够获取有效“声明”线程局部变量的ThreadLocal对象(在任何线程中)。

第二个条件是否意味着“声明”是全球性的?

IMO,没有。有两个原因。

  • 传统意义上的全局变量只有一个实例。本地线程为每个线程都有一个不同的实例。

  • ThreadLocal可供特定方法访问的事实不会使任何方法都可以访问它。 ThreadLocal的正常(和良好实践)使用模式是将对象引用保存在private static变量中。这意味着同一个类中的方法可以使用相应的线程局部变量实例...但其他类中的方法不能。

现在可以将ThreadLocal引用放在public static变量中,但为什么要这样做呢?您明确地创建了一个漏洞抽象 ...这可能会导致问题。

当然,你可以做@ Radiodef的回答所显示的内容;即创建一个本地线程,其实例只能从特定类的特定实例上的方法访问。但很难理解为什么你想要/需要达到那种限制水平。 (并且它可能导致存储泄漏......)

简短回答:如果您不希望可以访问线程局部变量,请限制对ThreadLocal对象的访问。

答案 1 :(得分:3)

只有局部变量在线程的堆栈上。*静态变量和实例变量都存在于堆上。如果我们想要,我们也可以自己传递一个ThreadLocal,而不会生活在一个对象或类中。

我们可以将ThreadLocal视为本地变量,可以在任何时间点访问

正常的局部变量在声明它们的范围时会被销毁,但ThreadLocal可以存在于任何地方。

  

所以我的问题是,是否存在一个多线程场景,其中ThreadLocal可以/需要应用于非静态字段?

我们可以提出一个......

interface Dial {}

class Gadget {
    ThreadLocal<Dial> d = new ThreadLocal<>();
}

class Gizmo implements Runnable {
    Gadget g;
    Gizmo(Gadget g) {
        this.g = g;
    }
    public void run() {}
}

{
    Gadget g = new Gadget();
    new Thread(new Gizmo(g)).start();
    new Thread(new Gizmo(g)).start();
}

两个线程共享同一个Gadget实例,但拥有自己的本地Dial

  

为什么我们需要ThreadLocal?

事实是,如果有的话,我们经常不需要ThreadLocal

*只有局部变量在线程的堆栈上,除非在理论上优化the JVM is allowed to do,其中对象可以堆栈分配。如果它发生,我们永远不会发现它,因为它不会被允许改变程序的行为。如果一个对象在线程之间共享,那么它就在堆上。

答案 2 :(得分:3)

static ThreadLocal<Whatever> threadLocal;

声明一个指向类ThreadLocal实例的字段。每个线程都有一个Map<ThreadLocal<?>, Object>,它将ThreadLocal的实例与相应的“线程局部变量”对此Thread的值相关联。也就是说,每个ThreadLocal实例标识一个“线程局部变量”,就JVM而言,ThreadLocal是一个类似于任何其他类的。

通常,当我们需要“线程局部变量”时,我们只需要一个,因此只创建一个ThreadLocal实例,它通常保存在静态字段中以方便访问。

如果我们需要为宿主对象的每个实例提供一个新的“线程局部变量”,我们可以简单地为每个宿主对象创建一个新的ThreadLocal实例,并存储在一个非静态字段中。在手边,我想不出这是最简单的解决方案,但我们可以做到。

答案 3 :(得分:2)

Threadlocal是一组变量的容器,每个变量只对一个线程可用,即它为每个线程提供包含类的实例。因此,ThreadLocal对象是全局可用的(在权限上花费),但是包含的类的每个实例仅在本地可用于该线程。

这意味着需要为每个线程稍微不同地初始化和销毁​​它,但在所有其他方面它与任何其他变量相同。

注意:破坏实例的目的是为了防止在从池中抽取线程时在同一线程上的独立调用之间发生内存泄漏或状态泄漏。

线程局部变量通常用于为线程提供线程安全选项,而不需要线程之间的通信。其中一个例子是在CDI中定义一个新的作用域,该作用域将完全存在于一个线程中(例如,您可以定义一个异步请求作用域)。

因此,无需将其限制为静态或非静态变量,只要它在需要的地方可访问即可。在默认的java环境中,静态变量是提供此类访问的便捷方式,但在CDI中,Singleton bean可以提供相同级别的访问,具有许多优点。

在注入的单例情形中,单例bean将包含对ThreadLocal的非静态引用,并提供访问其中包含的实例的服务。注入单例的引用和对ThreadLocal的单例引用都是非静态的。