1. Использование ThreadLocal
Один из способов предотвратить конфликты задач на общих ресурсах — исключить совместное использование переменных, используя локальное хранилище потока для создания разных хранилищ для разных потоков, использующих одну и ту же переменную.
Ниже приведен пример ThreadLocal. Здесь мы используем статическую глобальную переменную объекта ThreadLocal для хранения значения типа Integer. Передаем указанный номер в ThreadLocal для сохранения в разных потоках. Затем прочитайте его:
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String...args) {
threadLocal.set(-1);
ExecutorService executor = Executors.newCachedThreadPool();
for (int i=0; i<5; i++) {
final int ii = i; // i不能是final的,创建临时变量
executor.submit(new Runnable() {
public void run() {
threadLocal.set(ii);
System.out.println(threadLocal.get());
}
});
}
executor.shutdown();
System.out.println(threadLocal.get());
}
Из результатов выполнения программы видно, что каждый поток корректно считывает данные, сохраненные в ThreadLocal.
Итак, давайте резюмируем роль ThreadLocal в том, что переменные, хранящиеся в ThreadLocal, являются потокобезопасными, и каждый поток может читать только свое собственное сохраненное значение.
Экземпляры ThreadLocal обычно являются его использование, состоит в том, чтобы определить статический глобальный, и каждый нить использует его для чтения и записи только вами данных. Например, мы хотим создать базу данных для каждого потока потока, и соединение разрешено использовать только свой поток, поэтому его можно сохранить в ThreadLocal, а затем получить в использованном месте.
После прочтения приведенного выше примера у вас могут возникнуть следующие вопросы:
- Как гарантируется, что значение, хранящееся в ThreadLocal, является абсолютно потокобезопасным?
- Так где же хранятся эти значения?
- Является ли он статически типизированным или типизированным экземпляром?
- Если поток завершает выполнение и уничтожается, что произойдет с этими сохраненными значениями?
- ...
С этими вопросами выше, мы смотрим на исходный код в JDK ThreadLocal, как достичь.
2. Принцип работы ThreadLocal
Начнем с операции чтения.
Следующее находится в ThreadLocalset()
Код метода:
public T get() {
Thread t = Thread.currentThread(); // 1
ThreadLocalMap map = getMap(t); // 2
if (map != null) { // 3
ThreadLocalMap.Entry e = map.getEntry(this); // 4
if (e != null) {
T result = (T) e.value; // 5
return result;
}
}
return setInitialValue();
}
Здесь сначала экземпляр текущего потока получается на шаге 1, а затем передается на шаге 2.getMap()
метод, используя ThreadLocalMap текущего потока. Определение ThreadLocalMap здесь выглядит следующим образом:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
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);
}
// ...
}
Тогда давайте посмотрим наgetMap()
Определение метода:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Это на самом деле, когда мы звонимget()
метод, он сначала получит поле threadLocals в текущем потоке, которое имеет тип ThreadLocalMap. Затем мы получаем запись из хэш-таблицы, используя текущий экземпляр ThreadLocal в качестве ключа, а фактическое значение сохраняется в поле значения записи.
как вышеgetEntry()
Поскольку метод определен, кажется, что хеш-таблица здесь — это просто массив, так как же решить конфликт хэшей? На самом деле, мы знаем, что обычно есть два решения для решения хэш-коллизий: одно — это метод застежки-молнии, а другое — метод линейного обнаружения. бывший вHashMap
иConcurrentHashMap
Он больше используется в , а здесь фактически используется линейный метод детектирования. Грубо говоря, это поместить все значения в массив, а затем взять значение из массива в соответствии с результатом хеширования.Конкретный метод реализации можно увидеть в соответствующих точках знаний о структуре данных.
Отношения здесь ничуть не запутаны, давайте посмотрим:
Значение, которое мы сохраняем с помощью ThreadLocal, на самом деле хранится вThread
Используйте ThreadLocalMap, где значение экземпляра ThreadLocal действует как ключ хеш-таблицы:
Как показано на рисунке выше, если мы вызовем threadLocal1 в потоке thread1get()
Метод, первыйThread.currentThread()
Метод получает thread1, а затем получает экземпляр threadLocals thread1, threadLocals — это хэш-таблица типа ThreadLocalMap. Затем мы используем threadLocal1 в качестве ключа для получения значения Entry из threadLocals и извлечения сохраненного значения из Entry и возврата.
Пока что мы поняли принцип реализации ThreadLocal, и мы хотели его увидетьset()
метода, но основная истина уже раскрыта, так что нет необходимости продолжать.
3. Резюме
Давайте вернемся и посмотрим на некоторые из вопросов, которые мы задавали ранее:
- Как гарантируется, что значение, хранящееся в ThreadLocal, является абсолютно потокобезопасным?
На самом деле каждое значение существует внутри потока, а ThreadLocal используется только для того, чтобы помочь нам найти сохраненное значение из хэш-таблицы внутри потока. 2. Так где же хранятся эти значения? Поля экземпляра внутри потока. 3. Является ли он статически типизированным или типизированным экземпляром? Поля экземпляра внутри потока. 4. Если поток завершает выполнение и уничтожается, что произойдет с этими сохраненными значениями? Поскольку это поле является локальным для потока, значение исчезает при завершении потока.
Выше приведен принцип использования и реализации ThreadLocal.
Большое спасибо за внимание~