Использование ThreadLocal в тандеме с Volatile дает непредсказуемые результаты - PullRequest
1 голос
/ 24 января 2012

Я читал через модель Java Memory и играл с 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 ]

4 голосов
/ 24 января 2012

Как указывало большинство, вы глубоко неправильно поняли 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;
}

Для дальнейшего ответа на ваш вопрос о летучем поле.Это по сути бесполезно здесь.Поскольку само поле является «локальным для потока», проблем с упорядочением и памятью не будет.

4 голосов
/ 24 января 2012

В вашем коде вы создаете ссылку на ThreadLocal как локальную переменную вашего основного метода. Затем вы сохраняете экземпляр MyClass в нем и затем даете ту же ссылку MyClass на 5 потоков, созданных в основном методе.

Результирующий вывод программы непредсказуем, поскольку потоки не синхронизированы друг с другом. По крайней мере, один поток будет видеть флаг как false, остальные четыре могут видеть флаг как true или false в зависимости от того, как ОС запланирует выполнение потока. Возможно, что все 5 потоков увидят флаг как false, или 1 сможет увидеть его false, так и 4 увидят его true или что-то среднее.

Использование ThreadLocal никак не влияет на этот прогон, в зависимости от того, как вы его используете.

2 голосов
/ 24 января 2012

Только не угадай JVM.ThreadLocal - это обычный класс.Внутри он использует карту из текущего идентификатора потока в экземпляр объекта.Чтобы одна и та же переменная ThreadLocal могла иметь собственное значение для каждого потока.Это все.Ваша переменная существует только в главном потоке, поэтому она не имеет никакого смысла.

Volatile - это что-то в оптимизации Java-кода. Она просто останавливает все возможные оптимизации, которые позволяют избежать избыточных операций чтения / записи памяти и последовательности выполнения.повторно упорядоченности.Это важно для ожидания определенного поведения в многопоточной среде.

1 голос
/ 24 января 2012

У вас есть две большие проблемы:

1) Как отмечали многие, вы не используете ThreadLocal должным образом, поэтому у вас фактически нет никаких «локальных переменных».

2)Ваш код эквивалентен:

MyClass someInstance = new Class();
for (...)
   ... new Thread(someInstance);

, поэтому вы должны ожидать увидеть 1 on и 4 off.Однако ваш код плохо синхронизирован, поэтому вы получаете случайные результаты.Проблема в том, что, хотя вы объявляете flag как volatile, этого недостаточно для хорошей синхронизации, поскольку вы проверяете flag в printNameTillFlagSet, а затем изменяете значение flag сразу после вызова этого метода в run,Здесь есть пробел, где многие потоки могут видеть flag как истину.Вам следует проверить значение flag и изменить его в синхронизированном блоке .

0 голосов
/ 24 января 2012

Ваш основной поток, в котором у вас есть объект ThreadLocal, передается всем потокам. Таким образом, тот же экземпляр передается.

Так что это так же хорошо, как

new Thread(new MyClass());

Что вы можете попробовать, так это вызвать объект, вызываемый разными потоками, с локальной переменной потока. Это будет правильный тест для ThreadLocal, где каждый поток получит свой собственный экземпляр переменной.

...