将ThreadLocal与Volatile协同使用会产生不可预测的结果

时间:2012-01-24 14:09:20

标签: java volatile thread-local

我正在阅读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;       
    }


}

5 个答案:

答案 0 :(得分:4)

在代码中,您创建一个ThreadLocal引用作为main方法的局部变量。然后,在其中存储MyClass的实例,然后将MyClass的相同引用提供给main方法中创建的5个线程。

由于线程彼此不同步,因此程序的结果输出是不可预测的。至少有一个线程会将标志视为false,其他四个标志可以将标志视为truefalse,具体取决于操作系统如何调度线程执行。所有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的正确测试,其中每个线程将获得自己的变量实例。