Серия Java: принципы и сценарии использования ThreadLocal в интервью

Java задняя часть
Серия Java: принципы и сценарии использования ThreadLocal в интервью

Эта статья участвовала в третьем этапе тренировочного лагеря для создателей Nuggets. Подробности см.:Dig Li Project | Идет третий этап тренировочного лагеря создателя, «написание» личного влияния.


📖Предисловие

Сегодня блогер поделится с вами Java (обязательно для интервью): в интервьюThreadLocalПринципы и сценарии использования, если не нравится - не распыляйте, если есть возражения, добро пожаловать к обсуждению!

Я думаю, вас часто спрашивают в интервьюThreadLocalХотя я всегда знал о существовании этой вещи, я плохо изучил принцип, и у меня нет собственной системы знаний. Сегодня у Amway волна улучшений


🚀ThreadLocalчто

ThreadLocalдаJDK java.langИнструмент в пакете для реализации одних и тех же данных потока, совместно использующих различную изоляцию данных потока. Посмотрим, как это объясняется в исходном коде JDK: после перевода

ThreadLocalЭтот класс предоставляет локальные переменные потока, которые отличаются от других обычных переменных тем, что каждый поток, обращающийся к переменной, имеет внутри себя отдельную инициализированную копию переменной;ThreadLocalПеременные экземпляра обычно принимаютprivate staticУкрасьте в классе.

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

Одним словом этоThreadLocalПрименимо к каждому потоку, которому нужен собственный независимый экземпляр, и экземпляр должен использоваться в нескольких методах (совместное использование данных одного и того же потока), то есть переменные изолированы между потоками (разная изоляция данных потока) и совместно используются методами или классами. Сцены.


ThreadLocalиспользовать

 package com.test;

import java.util.concurrent.CountDownLatch;

/**
 * 
 * @Description: 我的测试类
 * @ClassName: MyDemo.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:25
 * @Email chen87647213@163.com
 */
public class MyDemo {

	private String string;

	private String getString() {
		return string;
	}

	private void setString(String string) {
		this.string = string;
	}

	public static void main(String[] args) {
		int threads = 9;
		MyDemo demo = new MyDemo();
		CountDownLatch countDownLatch = new CountDownLatch(threads);
		for (int i = 0; i < threads; i++) {
			Thread thread = new Thread(() -> {
				demo.setString(Thread.currentThread().getName());
				System.out.println("demo.getString()================>" + demo.getString());
				countDownLatch.countDown();
			}, "执行线程 - " + i);
			thread.start();
		}

	}

}

консольный вывод

demo.getString()================>执行线程 - 0
demo.getString()================>执行线程 - 1
demo.getString()================>执行线程 - 2
demo.getString()================>执行线程 - 3
demo.getString()================>执行线程 - 4
demo.getString()================>执行线程 - 6
demo.getString()================>执行线程 - 7
demo.getString()================>执行线程 - 5
demo.getString()================>执行线程 - 8

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

package com.test;

import java.util.concurrent.CountDownLatch;

/**
 * 
 * @Description: 我的测试类
 * @ClassName: MyThreadLocalDemo.java
 * @author ChenYongJia
 * @Date 2019年4月17日 晚上23:28
 * @Email chen87647213@163.com
 */
public class MyThreadLocalDemo {

	private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

	private String getString() {
		return threadLocal.get();
	}

	private void setString(String string) {
		threadLocal.set(string);
	}

	public static void main(String[] args) {
		int threads = 9;
		MyThreadLocalDemo demo = new MyThreadLocalDemo();
		CountDownLatch countDownLatch = new CountDownLatch(threads);
		for (int i = 0; i < threads; i++) {
			Thread thread = new Thread(() -> {
				demo.setString(Thread.currentThread().getName());
				System.out.println("demo.getString()================>" + demo.getString());
				countDownLatch.countDown();
			}, "执行线程 - " + i);
			thread.start();
		}
	}

}

консольный вывод

demo.getString()================>执行线程 - 0
demo.getString()================>执行线程 - 1
demo.getString()================>执行线程 - 4
demo.getString()================>执行线程 - 3
demo.getString()================>执行线程 - 2
demo.getString()================>执行线程 - 7
demo.getString()================>执行线程 - 5
demo.getString()================>执行线程 - 8
demo.getString()================>执行线程 - 6

Судя по результатам, очень удобно решать проблему изоляции данных между несколькими потоками.

Некоторые друзья здесь могут подумать, что в примере 1 мы можем реализовать эту функцию с помощью блокировки.Да, блокировка действительно может решить эту проблему, но здесь мы подчеркиваем проблему изоляции потоков данных, а не проблему многопоточного совместного использования данных. Если мы здесь, кромеgetString()Есть много других способов использовать этоString, в настоящее время нет явного процесса передачи данных между различными методами, и они могут быть напрямуюThreadLocalпеременная, этоThreadLocalЯдро одного и того же потока данных разделяет другую изоляцию данных потока.

так какThreadLocalдля поддержки дженериков, здесь для храненияStringЧтобы продемонстрировать, на самом деле можно сохранить любой тип, и эффект тот же.


🐱‍🏍ThreadLocalАнализ исходного кода

Прежде чем анализировать исходный код, мы понимаем одну вещь, что экземпляр объекта иThreadLocalОтношение отображения переменных определяется потокомThreadДля обслуживания экземпляр объекта иThreadLocalОтношение отображения переменных определяется потокомThreadДля обслуживания экземпляр объекта иThreadLocalОтношение отображения переменных определяется потокомThreadподдерживать. Важные вещи говорятся трижды.

Фактически экземпляр объекта иThreadLocalОтношение отображения переменных хранится вMapвнутри (этоMapявляется абстрактнымMapнетjava.utilсерединаMap), при этомMapдаThreadПоле класс! И реальные отношения сопоставления хранилищаMapто естьThreadLocalMap. Давайте посмотрим на конкретную реализацию через несколько методов в исходном коде.

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

//获取线程中的ThreadLocalMap 字段!!
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

//创建线程的变量
void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
}

существуетsetМетод сначала получает текущий поток, а затем передаетgetMapПолучить текущий потокThreadLocalMapпеременная типаthreadLocals, если он существует, он будет назначен напрямую, если он не существует, он будет создан для потокаThreadLocalMapпеременные и присваивать значения. при назначенииthisэто сам экземпляр объекта, который вызывает переменную.

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();
}


private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

getЭтот метод также относительно прост, и он также сначала получает текущий поток.ThreadLocalMapПеременная, которая возвращает значение, если оно существует, или создает и возвращает начальное значение, если оно не существует.


🎶ThreadLocalMapАнализ исходного кода

ThreadLocalБазовая реализация осуществляется черезThreadLocalMapЧтобы достичь этого, давайте сначала посмотримThreadLocalMapопределение, а затем посмотрите на соответствующееsetа такжеgetметод.

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;
}

ThreadLocalMapиспользуется вEntry[]Массивы используются для хранения отношений между экземплярами объектов и переменными, а объекты-экземпляры используются какkey, переменная какvalueОсознайте соответствующие отношения. и вотkeyИспользуются слабые ссылки на объекты-экземпляры (поскольку нашаkeyЭто экземпляр объекта. Каждый экземпляр объекта имеет свой жизненный цикл. Здесь можно использовать слабую ссылку для ссылки на него, не влияя на жизненный цикл экземпляра объекта).

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    //获取 hash 值,用于数组中的下标
    int i = key.threadLocalHashCode & (len-1);

    //如果数组该位置有对象则进入
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        //k 相等则覆盖旧值
        if (k == key) {
            e.value = value;
            return;
        }

        //此时说明此处 Entry 的 k 中的对象实例已经被回收了,需要替换掉这个位置的 key 和 value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    //创建 Entry 对象
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


//获取 Entry
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

закончить смотретьThreadLocalСвязанныйJDK源码, У меня самого есть определенное понимание, и я надеюсь, что это может помочь всем.*


Иди сюда Java (требуется для собеседований): На собеседованииThreadLocalПринцип и сценарии использования поделились, пробуйте!


🎉 Наконец-то

  • Для обработки транзакций на сервисном уровне вы можете использовать это для сохранения объекта Connection.

  • Дополнительные справочные сообщения в блоге см. здесь:"Блог Чен Юнцзя"

  • Друзья, которым нравятся блогеры, могут подписаться, поставить лайк и продолжать обновлять, хе-хе!