【问题标题】:ThreadLocal: why changes made by one thread is visible in other threadThreadLocal:为什么一个线程所做的更改在另一个线程中可见
【发布时间】:2022-02-03 18:48:45
【问题描述】:

考虑以下代码:

public static void main(String args[]) {
        
        
        Set<Integer> set=new HashSet<>();
       set.add(1);
        
        ThreadLocal<Set> var1=new ThreadLocal<>();
        
        //THREAD1
        Runnable r=()->{
                    
            var1.set(set);

            for(int i=1;i<=10;i++)
             System.out.println("Thread ID:"+Thread.currentThread()+":  "+var1.get());
            };

        Thread th=new Thread(r);
        th.start();
        
        //THREAD2
        Thread th1=new Thread(()-> {
                
            var1.set(set);
            
            var1.get().remove(1); //removing 1 from this thread copy of set.
            var1.get().add(2);    //adding 2 to this thread copy of set 
            
            for(int i=1;i<=10;i++)
             System.out.println("Thread ID:"+Thread.currentThread()+":  "+var1.get());
             
        });
        th1.start();
        
        
    }

输出:

Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-0,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]
Thread ID:Thread[Thread-1,5,main]:  [2]

为什么 Thread2 所做的更改(即删除 1 和添加 2)对 thread1 也是可见的?这里的理想用例是什么?

【问题讨论】:

  • ThreadLocal是在子线程启动之前在主线程中创建的,所以只有一个实例。
  • @JimGarrison,应该只有一个 ThreadLocal 的实例@ 这就是重点。 ThreadLocal&lt;t&gt; 实际上是 Map&lt;threadID,t&gt;,其中 get()set(v) 方法使用调用者的线程 ID 作为键。

标签: java multithreading thread-safety


【解决方案1】:

在学习编程时,您可能听说过变量是一个带有名称的盒子的比喻,您可以在其中存储一些东西。如果你以后想看那个东西,你可以找到正确名称的盒子并往里面看。

本地线程就像一个魔盒。它的内容取决于谁在看。每个线程都可以将不同的东西放在同一个盒子里,以后再访问它。

在您的示例中,您有两个线程,每个线程都有自己对您命名为var1 的魔术盒的视图。但是,这些线程中的每一个都将同一个对象放入盒子中——恰如其分地命名为set对象。

因为两个线程只使用一个集合实例,所以每个线程的动作相互干扰。以这种方式使用本地线程是没有意义的;相当于每个线程直接访问变量set

ThreadLocal 的一个更有用的应用程序会为每个线程中的本地线程分配不同的值。在您的示例中,这意味着在每个 Runnable 中创建另一个 HashSet 实例。这保持了每个值与一个线程的隔离,并避免了代码中的并发错误。

在底层,ThreadLocal 本质上是一个映射,其中键是线程,值是分配给该线程中引用的值。

我不确定ThreadLocal 是否有“理想”用例;当无法实现理想时,我将其视为解决方法的工具。我认为它们是必要的邪恶,最后的手段。但是,例如,当无法作为方法参数传递时,您可以使用 ThreadLocal 在堆栈上下传递额外数据。

【讨论】:

  • Re,“值的副本......变量的副本......” IMO 这可能会让新手感到困惑。最好先解释Set instance,Set variable 之间的区别(包含对 Set 实例的引用。)这是一个很多 Java 新手似乎都在为之奋斗的概念。一旦他们理解了这种区别,就更容易解释 ThreadLocal 如何是 references 的集合, 以及 OP 的程序如何将 same 引用分配给每个插槽。
猜你喜欢
  • 2012-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-14
  • 2013-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多