Углубленный анализ инструмента параллелизма Java ThreadLocal

Что такое ThreadLocal

ThreadLocal — это инструментальный класс в Java, предоставляющий локальные переменные потока. Это означает, что он может создать копию переменной для каждого используемого потока, и каждый поток изолируется при доступе к копии переменной, не мешая друг другу.

Основные функции ThreadLocal:

  1. Изоляция потоков: каждый поток имеет собственную копию переменных и не влияет друг на друга.
  2. Избегайте передачи параметров: передавайте данные через ThreadLocal, нет необходимости передавать параметры между методами.
    Давайте посмотрим на пример:
public class ThreadLocalExample {
    
    
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void add() {
    
    
        threadLocal.set(threadLocal.get() + 1);
    }

    public static Integer get() {
    
    
        return threadLocal.get();
    }

    public static void main(String[] args) {
    
    
        Thread thread1 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 3; i++) {
    
    
                    add();
                    System.out.println(get());
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 3; i++) {
    
    
                    add();
                    System.out.println(get());
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

Результаты вывода:
1
2
3
1
2
3
Видно, что каждый поток имеет независимую копию переменной threadLocal, и каждый накапливает и печатает свое собственное значение threadLocal, не влияя друг на друга. Этим достигается эффект изоляции потоков.
Если вы не используете ThreadLocal, используйте общие переменные, например:

public class NoThreadLocalExample {
    
    
    private static int num;

    public static void add() {
    
    
        num++;
    }

    public static int get() {
    
    
        return num; 
    }

    public static void main(String[] args) {
    
    
        // ...
    }
}

Вывод будет не по порядку, потому что два потока будут конкурировать за изменение и чтение общей переменной num:
1
3
2
5
4
Это проблема, заключающаяся в том, что несколько потоков будут влиять друг на друга, когда ThreadLocal не используется.
В отношении ThreadLocal следует отметить три основных момента:

  1. Риск утечки памяти: ThreadLocal использует слабые ссылки для хранения данных. Если нет внешних сильных ссылок, это может привести к утечке памяти после остановки потока. Поэтому после использования ThreadLocal вызовите метод remove(), чтобы очистить данные.
  2. Безопасность потоков: ThreadLocal сам по себе является потокобезопасным, но объекты данных, хранящиеся в ThreadLocal, не обязательно являются потокобезопасными. Если несколько потоков работают с одним и тем же объектом данных, все равно необходимо учитывать синхронизацию потоков.
  3. Наследование: дочерний поток не может получить данные, заданные родительским потоком для ThreadLocal. Каждый поток имеет свою собственную копию переменной.
    Резюме:
    ThreadLocal обеспечивает независимую копию переменной для каждого потока, обеспечивая изоляцию потока и избегая эффекта передачи параметров. Но также существует риск утечки памяти, и необходимо вызывать метод remove() для очистки данных после использования. А ThreadLocal предоставляет копию переменной только для одного потока, и дочерний поток не может получить данные родительского потока.
    ThreadLocal подходит для сценариев, в которых переменные изолированы между потоками и передаются в контексте цепочки вызовов методов. Его внешний вид позволяет нам писать код более просто и элегантно в среде с высокой степенью параллелизма.

Каков внутренний принцип реализации ThreadLocal?

ThreadLocal использует внутренний класс ThreadLocalMap для хранения переменных копирования каждого потока. ThreadLocalMap использует ссылки на потоки в качестве ключей, а копии переменных хранятся как значения в хеш-таблице.
Когда вызывается метод get() класса ThreadLocal, сначала будет получен текущий поток, а затем будет получена копия переменной из ThreadLocalMap, связанного с этим потоком. Метод set() аналогичен, он установит значение ThreadLocalMap текущего потока.
Посмотрите на исходный код реализации ThreadLocal:

public T get() {
    
     
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t); 
    if (map != null) {
    
    
        ThreadLocalMap.Entry e = map.getEntry(this); 
        if (e != null) {
    
    
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    
    
    return t.threadLocals; 
} 

public void set(T value) {
    
    
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); 
}   

void createMap(Thread t, T firstValue) {
    
    
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Мы видим, что:

  1. Получить текущий поток через Thread.currentThread()
  2. Получить объект ThreadLocalMap потока с помощью метода getMap()
  3. Если существует ThreadLocalMap, получите или установите из него значение локальной переменной потока.
  4. Если ThreadLocalMap не существует, создайте объект ThreadLocalMap и свяжите его с потоком перед установкой значения.
    Таким образом, каждый поток поддерживает ссылку ThreadLocalMap, которая хранит копию переменной с ThreadLocal в качестве ключа и реализует изоляцию переменных каждого потока.
    А сам ThreadLocal используется только как ключ для идентификации переменных и не содержит конкретных значений, что также является основой для его реализации изоляции потоков.

ThreadLocal — важный механизм для реализации локальных переменных потока в Java. Он создает копию переменной для каждого потока, так что каждый поток может независимо изменять свою собственную копию, не затрагивая соответствующие копии других потоков.
Принцип реализации ThreadLocal заключается в том, что он использует внутренний класс ThreadLocalMap для поддержки карты копий переменных для каждого потока. ThreadLocalMap использует ссылки на потоки в качестве ключей, а копии переменных хранятся как значения в хеш-таблице.
При доступе или настройке копии переменной через ThreadLocal сначала будет получена ссылка на текущий поток, а затем получено или установлено соответствующее значение в связанном ThreadLocalMap через этот поток. Таким образом, для каждого потока создается независимая копия переменной, что обеспечивает эффект изоляции потока.
Основной API ThreadLocal имеет только три метода: get(), set() и remove(). Это просто и мощно, и полное понимание принесет много удобства в наше многопоточное программирование.
Сценарии применения ThreadLocal в основном включают в себя: подключение к базе данных, управление транзакциями, передачу пользовательского контекста, предотвращение передачи параметров и т. д. Он подходит для сценариев, в которых переменные изолированы между потоками и должны передаваться в цепочке вызовов методов.
Но при использовании ThreadLocal также нужно обратить внимание на три момента:

  1. Утечка памяти: поскольку ThreadLocal использует слабые ссылки, отсутствие копии внешней сильной ссылки может привести к утечке памяти. Поэтому после его использования вызовите метод remove(), чтобы удалить ссылку.
  2. Потокобезопасность: объекты, хранящиеся в ThreadLocal, должны быть потокобезопасными, иначе в многопоточной среде возникнут проблемы.
  3. Чрезмерное использование: неправильное использование ThreadLocal может затруднить понимание и поддержку кода.

Используйте ThreadLocal для реализации управления транзакциями:

  1. Определите переменную ThreadLocal для хранения соединения:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
  1. Получите соединение из источника данных при запуске транзакции и сохраните его в ThreadLocal:
public void beginTransaction() throws SQLException {
    
    
    Connection conn = DataSourceUtils.getConnection();
    connectionHolder.set(conn);
    conn.setAutoCommit(false);
}
  1. Используйте метод get() в транзакции, чтобы получить Connection из ThreadLocal:
public void doSomeOperation() {
    
    
    Connection conn = connectionHolder.get();
    // 使用 conn 执行 SQL 操作
} 
  1. При фиксации или откате транзакции получите Connection от ThreadLocal и зафиксируйте или откатите:
public void commitTransaction() throws SQLException {
    
    
    Connection conn = connectionHolder.get();
    conn.commit();
    conn.close();
    connectionHolder.remove();
}

public void rollbackTransaction() throws SQLException {
    
    
    Connection conn = connectionHolder.get();
    conn.rollback();
    conn.close();
    connectionHolder.remove(); 
}
  1. Вызовите remove() после транзакции, чтобы удалить Connection из ThreadLocal.
    Таким образом, доступ к Connection через ThreadLocal может реализовать привязку транзакции и потока и гарантировать, что каждый поток имеет свое собственное независимое Connection для управления транзакциями.
    Это идея использования ThreadLocal для реализации простого управления транзакциями.Более надежная структура управления транзакциями будет включать больше контента, но ThreadLocal по-прежнему является ключом к изоляции потоков.

Acho que você gosta

Origin blog.csdn.net/pengjun_ge/article/details/131347197
Recomendado
Clasificación