我正在阅读Java内存模型并且正在使用volatile。我想检查Volatile如何与ThreadLocal协同工作。根据定义,ThreadLocal有自己独立初始化的变量副本,而当您使用volatile关键字时,JVM保证所有写入和后续读取都直接从内存完成。基于高级定义,我知道我想要做的事情将会产生不可预测的结果。但出于好奇,我想问一下是否有人可以解释更多细节,好像在后台发生了什么。这是我的代码供你参考......
public class MyMainClass {
public static void main(String[] args) throws InterruptedException {
ThreadLocal<MyClass> local = new ThreadLocal<>();
local.set(new MyClass());
for(int i=0;i<5; i++){
Thread thread = new Thread(local.get());
thread.start();
}
}
}
public class MyClass implements Runnable {
private volatile boolean flag = false;
public void printNameTillFlagIsSet(){
if(!flag)
System.out.println("Flag is on for : " + Thread.currentThread().getName());
else
System.out.println("Flag is off for : " + Thread.currentThread().getName());
}
@Override
public void run() {
printNameTillFlagIsSet();
this.flag = true;
}
}
答案 0 :(得分:4)
在代码中,您创建一个ThreadLocal引用作为main方法的局部变量。然后,在其中存储MyClass
的实例,然后将MyClass
的相同引用提供给main方法中创建的5个线程。
由于线程彼此不同步,因此程序的结果输出是不可预测的。至少有一个线程会将标志视为false
,其他四个标志可以将标志视为true
或false
,具体取决于操作系统如何调度线程执行。所有5个主题可能会将标记显示为false
,或者1可以看到它false
,4可以看到它true
或其间的任何内容。
根据您的使用方式,使用ThreadLocal对此次运行没有任何影响。
答案 1 :(得分:4)
正如大多数人所指出的,你已经深深地误解了ThreadLocal。这就是我如何写它以使其更准确。
public class MyMainClass {
private static final ThreadLocal<MyClass> local = new ThreadLocal<>(){
public MyClass initialValue(){
return new MyClass();
}
}
public static void main(String[] args) throws InterruptedException {
local.set(new MyClass());
for(int i=0;i<5; i++){
Thread thread = new Thread(new Runnable(){
public void run(){
local.get().printNameTillFlagIsSet();
local.get().run();
local.get().printNameTillFlagIsSet();
}
});
thread.start();
}
}
}
所以这里创建了五个不同的MyClass实例。每个线程都有自己的每个MyClass的可访问副本。那就是在i = 0时创建的线程将始终具有不同的MyClass实例,然后i = 1,2,3,4,尽管已完成了多少local.get()。
内部工作有点复杂,但可以类似于
ConcurrentMap<Long,Thread> threadLocalMap =...;
public MyClass get(){
long id = Thread.currentThread().getId();
MyClass value = threadLocalMap.get(id);
if(value == null){
value = initialValue();
threadLocalMap.put(id,value);
}
return value;
}
进一步回答有关易变场的问题。在这里它本质上是无用的。由于字段本身是“线程本地的”,因此不会出现排序/内存问题。
答案 2 :(得分:2)
只是不要将JVM神化。 ThreadLocal是一个常规类。在里面它使用从当前线程ID到对象实例的映射。因此,相同的ThreadLocal变量可以为每个线程提供自己的值。就这样。你的变量只存在于主线程中,因此它没有任何意义。
volatile是关于java代码优化的东西,它只是停止所有可能的优化,允许避免冗余内存读/写和执行顺序重新排序。在多线程环境中期待某些特定行为非常重要。
答案 3 :(得分:1)
你有两大问题:
1)正如许多人所指出的那样,你没有正确使用ThreadLocal,所以你实际上没有任何“线程本地”变量。
2)您的代码相当于:
MyClass someInstance = new Class();
for (...)
... new Thread(someInstance);
因此您应该看到1 on
和4 off
。但是,您的代码严重同步,因此您可以获得随机结果。问题是虽然您将flag
声明为volatile
,但这对于良好的同步是不够的,因为您在printNameTillFlagSet中对flag
进行了检查,然后仅更改了flag
值在run
之后调用该方法。这里有一个空白,许多线程可以看到flag
为真。您应该检查flag
值并在同步块中更改 。
答案 4 :(得分:0)
您将ThreadLocal对象的主线程传递给所有线程。所以相同的实例正在通过。
所以它和
一样好new Thread(new MyClass());
您可以尝试的是让不同线程使用线程局部变量调用对象。这将是ThreadLocal的正确测试,其中每个线程将获得自己的变量实例。