Как правило, семьям с несколькими детьми приходится покупать более одной игрушки. Если вы просто купите один, это будет более захватывающим. Этоне делиться, давать каждому ребенку игрушку соответствует нашей Java, то есть каждый поток имеет свои локальные переменные, мы играем со своими, избегаем конфликтов и живем в гармонии, чтобы сделать потоки безопасными.
Ява прошлаThreadLocal
для реализации локального хранилища потоков.
Эта идея тоже очень понятна, то есть у каждого потока должна быть своя локальная переменная, тогда сделайте приватное свойство в Thread.ThreadLocal.ThreadLocalMap threadLocals = null;
Это отношение показано на рисунке ниже
ThreadLocal
Простое приложение выглядит следующим образом
public class Demo {
private static final ThreadLocal<Foo> fooLocal = new ThreadLocal<Foo>();
public static Foo getFoo() {
return fooLocal.get();
}
public static void setFoo(Foo foo) {
fooLocal.set(foo);
}
}
Присмотритесь к тому, что происходит внутри,ThreadLocalMap
даThreadLocal
внутренний статический класс, который хотя и называетсяMap
Но иjava.util.Map
Это не связано, просто он реализует такие функции, какMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
можно увидетьThreadLocalMap
EстьEntry
Массивы, только массивы не имеют ничего подобногоHashMap
Таким образом, существует связанный список, поэтому, когда хэши сталкиваются,ThreadLocalMap
даИспользуйте линейное обнаружение для разрешения конфликтов хэшей.
Линейное обнаружение сначала основано на начальномkey
изhashcode
Значение определяет, что элемент находится вtable
позиция в массиве, если уже есть другаяkey
Если элемент значения занят, используется фиксированный алгоритм для поиска следующей позиции с определенным размером шага, пока не будет найдена позиция, которую можно сохранить. существуетThreadLocalMap
Размер шага равен 1.
Разрешать хеш-конфликты таким образом очень неэффективно, поэтому обратите внимание на количество ThreadLocals.
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
и вы можете видеть этоEntry
ПучокThreadLocal
В качестве ключа используется слабая ссылка. Так зачем создавать слабую ссылку (пока существует объект слабой ссылки GC, он будет переработан)?
во-первыхThreadLocal
Никакое значение не хранится внутри, его роль только тогда, когда нашThreadLocalMap的key
, чтобы поток мог получить соответствующийvalue
. Когда нам не нужно использовать этот ключ, мы ставимfooLocal=null
Таким образом, сильные ссылки исчезли. Предполагая, что в Записи также есть сильная ссылка, она равна этомуThreadLocal
По-прежнему существует сильная ссылка на экземпляр, поэтому мы хотим, чтобы сборщик мусора переработалfooLocal
Его нельзя переработать. Тогда некоторые люди могут подумать, что вам не очень опасно делать слабые ссылки, на случай, если GC исчезнет? Не бойся, покаfooLocal
Эта сильная ссылка находится в этомThreadLocal
Экземпляры не будут переработаны. (Сильные и слабые ссылки см. в моей предыдущей статьеРазница между четырьмя методами цитирования)
Таким образом, слабые ссылки в основном делают бесполезнымиThreadLocal
Очищено ГК.
Кто-то тут может спросить, что ключ сброшен, что делать со значением, а Entry все равно есть. Да, при использовании пула потоков из-за длительного жизненного цикла потока после удаления ключа некоторых больших объектов всегда существующее значение может вызвать утечку памяти.
Но java это учитывает. при звонкеget()、set()
Метод найдет запись, ключ которой был убит, и уничтожит ее. и обеспечиваетremove()
метод. Несмотря на то чтоget()、set()
будет убиратьkey
заnull的Entry
, но очищается не при каждом вызове, а только приget
Когда хэш пропущен напрямую илиset
Иногда хэш попадает не напрямую, когда линейное обнаружение запущено, оно будет очищено, когда ключ будет нулевым.
//get 方法
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); //直接没命中
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) { //开始探测了
ThreadLocal<?> k = e.get();
if (k == key) //命中了就返回
return e;
if (k == null) //探测到key是null的就清理
expungeStaleEntry(i);
else
i = nextIndex(i, len); //否则继续
e = tab[i];
}
return null;
}
//set 方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value; //如果已经有就替换原有的value
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
Поэтому, когда нет необходимостиthreadlocal
Он по-прежнему показывает вызов, когдаremove()
метод лучше.
Эпилог
Суть локального хранилища потоков заключается в следующем.не делиться, обратите внимание на проблему утечки памяти и проблему коллизии хэшей. Он по-прежнему широко используется, например, транзакции веснойthreadlocal
.
Если есть ошибки, поправьте меня!
Личный публичный аккаунт: стратегия прокачки да
Имеются соответствующие расширенные материалы интервью (распространение, настройка производительности, классическая книга в формате pdf), ожидающие сбора.