Разбираем подробно Java ThreadLocal

Пример локальной переменной потока Java. Java 8 ThreadLocal в примере начального метода, потокобезопасные переменные.

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

ThreadLocal – класс, позволяющий имея одну переменную, иметь различное её значение для каждого из потоков.

У каждого потока – т.е. экземпляра класса Thread – есть ассоциированная с ним таблица ThreadLocal-переменных. Ключами таблицы являются cсылки на объекты класса ThreadLocal, а значениями – ссылки на объекты, «захваченные» ThreadLocal-переменными, т.е. ThreadLocal-переменные отличаются от обычных переменных тем, что у каждого потока свой собственный, индивидуально инициализируемый экземпляр переменной. Доступ к значению можно получить через методы get() или set().

Например, если мы объявим ThreadLocal-переменную: ThreadLocal<Object> locals = new ThreadLocal<Object>();. А затем, в потоке, сделаем locals.set(myObject), то ключом таблицы будет ссылка на объект locals, а значением – ссылка на объект myObject. При этом для другого потока существует возможность «положить» внутрь locals другое значение.

Следует обратить внимание, что ThreadLocal изолирует именно ссылки на объекты, а не сами объекты. Если изолированные внутри потоков ссылки ведут на один и тот же объект, то возможны коллизии.

Так же важно отметить, что т.к. ThreadLocal-переменные изолированы в потоках, то инициализация такой переменной должна происходить в том же потоке, в котором она будет использоваться. Ошибкой является инициализация такой переменной (вызов метода set()) в главном потоке приложения, потому как в данном случае значение, переданное в методе set(), будет «захвачено» для главного потока, и при вызове метода get() в целевом потоке будет возвращен null.

Java ThreadLocal

У каждого потока есть своя собственная переменная ThreadLocal , и они могут использовать методы get() и set (), чтобы получить значение по умолчанию или изменить его значение локально для потока.

Экземпляры ThreadLocal обычно являются частными статическими полями в классах, которые хотят связать состояние с потоком.

Пример Java ThreadLocal

Вот небольшой пример, показывающий использование ThreadLocal в java-программе и доказывающий, что каждый поток имеет свою собственную копию переменной ThreadLocal.

Вот небольшой пример, показывающий использование ThreadLocal в java-программе и доказывающий, что каждый поток имеет свою собственную копию переменной ThreadLocal.

package com.journaldev.threads;

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal formatter = new ThreadLocal(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };
    
    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //formatter pattern is changed here by thread, but it won't reflect to other threads
        formatter.set(new SimpleDateFormat());
        
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}

Результатом приведенного выше примера программы java ThreadLocal является:

Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = M/d/yy h:mm a
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = M/d/yy h:mm a
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 4 formatter = M/d/yy h:mm a
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = M/d/yy h:mm a
Thread Name= 3 formatter = M/d/yy h:mm a
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = M/d/yy h:mm a
Thread Name= 6 formatter = M/d/yy h:mm a
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = M/d/yy h:mm a
Thread Name= 7 formatter = M/d/yy h:mm a
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 9 formatter = M/d/yy h:mm a

Как вы можете видеть из выходных данных, Thread-0 изменил значение форматирования, но по-прежнему форматирование по умолчанию thread-2 совпадает с инициализированным значением. Вы можете увидеть тот же шаблон и для других потоков.

Обновление : Класс ThreadLocal расширен в Java 8 с помощью нового метода с Initial () , который принимает в качестве аргумента функциональный интерфейс поставщика. Таким образом, мы можем использовать лямбда-выражения для простого создания экземпляра ThreadLocal. Например, приведенная выше локальная переменная форматирования может быть определена в одной строке, как показано ниже:

private static final ThreadLocal formatter = 
	ThreadLocal.withInitial
	(() -> {return new SimpleDateFormat("yyyyMMdd HHmm");});

Это все для ThreadLocal в на java.

Ссылка: API Doc

Ответить