Заметки по изучению стереотипов Dark Horse Station B

Адрес видео: https://www.yuque.com/linxun-bpyj0/linxun/vy91es9lyg7kbfnr

контур

Базовый

Основные моменты: алгоритм, структура данных, базовый шаблон проектирования.

1. Бинарный поиск

Требовать

  • Уметь описать алгоритм бинарного поиска на своем родном языке
  • Возможность написания кода бинарного поиска от руки
  • Быть в состоянии ответить на некоторые измененные методы тестирования

Алгоритм Описание

  1. Предпосылка: существует отсортированный массив A (при условии, что это было сделано)
  2. Задайте левую границу L и правую границу R, определите диапазон поиска и выполните бинарный поиск в цикле (шаги 3 и 4)
  3. Получить промежуточный индекс M = Floor((L+R)/2)
  4. Значение A[M] промежуточного индекса сравнивается с искомым значением T
    ① A[M] == T означает найдено, возвращает промежуточный индекс
    ② A[M] > T, другие элементы в правой части промежуточное значение больше, чем T, не нужно сравнивать, найдите средний индекс слева, установите M - 1 в качестве правой границы и повторите поиск
    ③ A[M] < T, другие элементы слева от среднего значения меньше Т, сравнивать не надо, найти средний индекс справа, поставить М+1 Для левой границы заново найти
  5. Когда L > R, это означает, что не найдено, и цикл должен закончиться.

Для более яркого описания, пожалуйста, обратитесь к: binary_search.html

Реализация алгоритма

public static int binarySearch(int[] a, int t) {
    int l = 0, r = a.length - 1, m;
    while (l <= r) {
        m = (l + r) / 2;
        if (a[m] == t) {
            return m;
        } else if (a[m] > t) {
            r = m - 1;
        } else {
            l = m + 1;
        }
    }
    return -1;
}

тестовый код

public static void main(String[] args) {
    int[] array = {1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50};
    int target = 47;
    int idx = binarySearch(array, target);
    System.out.println(idx);
}

Решить проблему целочисленного переполнения

Если значения l и r велики, l + rэто может привести к выходу за пределы диапазона целых чисел и вызвать ошибки в работе. Есть два решения:

int m = l + (r - l) / 2;

Другой:

int m = (l + r) >>> 1;

Другие методы

  1. Существует упорядоченный список 1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50. Когда найден узел с двоичным значением поиска 48, количество сравнений, необходимое для успешный поиск
  2. При использовании метода дихотомии для нахождения элемента 81 в последовательности 1,4,6,7,15,33,39,50,64,78,75,81,89,96 необходимо пройти () сравнения
  3. Для двоичного поиска числа в массиве из 128 элементов максимальное количество сравнений требуется не более, чем сколько раз

Для первых двух вопросов запомните краткую формулу судейства: два нечетных очка занимают середину, а четные два очка – середину слева. Для последнего вопроса нужно знать формулу:

Где n — количество поисков, а N — количество элементов

2. Пузырьковая сортировка

Требовать

  • Уметь описать алгоритм пузырьковой сортировки на своем родном языке.
  • Возможность написать код пузырьковой сортировки вручную
  • Понимать некоторые методы оптимизации пузырьковой сортировки.

Алгоритм Описание

  1. Сравните размеры двух соседних элементов в массиве по очереди. Если a[j] > a[j+1], то поменяйте местами два элемента. Сравнение обоих из них называется раундом всплытия. Результатом является расположение наибольшего элемент, чтобы наконец
  2. Повторяйте вышеуказанные шаги, пока весь массив не будет отсортирован.

Более наглядное описание см. на странице bubble_sort.html.

Реализация алгоритма

public static void bubble(int[] a) {
    for (int j = 0; j < a.length - 1; j++) {
        // 一轮冒泡
        boolean swapped = false; // 是否发生了交换
        for (int i = 0; i < a.length - 1 - j; i++) {
            System.out.println("比较次数" + i);
            if (a[i] > a[i + 1]) {
                Utils.swap(a, i, i + 1);
                swapped = true;
            }
        }
        System.out.println("第" + j + "轮冒泡"
                           + Arrays.toString(a));
        if (!swapped) {
            break;
        }
    }
}
  • Точка оптимизации 1: после каждого раунда барботирования внутренний цикл можно сократить один раз.
  • Точка оптимизации 2: если в каком-то раунде барботирования нет обмена, значит, все данные в порядке, и внешний цикл можно завершить

расширенная оптимизация

public static void bubble_v2(int[] a) {
    int n = a.length - 1;
    while (true) {
        int last = 0; // 表示最后一次交换索引位置
        for (int i = 0; i < n; i++) {
            System.out.println("比较次数" + i);
            if (a[i] > a[i + 1]) {
                Utils.swap(a, i, i + 1);
                last = i;
            }
        }
        n = last;
        System.out.println("第轮冒泡"
                           + Arrays.toString(a));
        if (n == 0) {
            break;
        }
    }
}
  • Во время каждого раунда барботирования последний обменный индекс может использоваться как количество сравнений для следующего раунда барботирования.Если это значение равно нулю, это означает, что весь массив в порядке, и вы можете просто выйти из внешнего цикла

3. Сортировка выбором

Требовать

  • Уметь описать алгоритм сортировки выбором на своем родном языке
  • Возможность сравнения сортировки выбором с пузырьковой сортировкой
  • Понимание нестабильной сортировки и стабильной сортировки

Алгоритм Описание

  1. Разделите массив на два подмножества, отсортированное и несортированное, каждый раунд выбирает наименьший элемент из несортированного подмножества и помещает его в отсортированное подмножество.
  2. Повторяйте вышеуказанные шаги, пока весь массив не будет отсортирован.

Более наглядное описание см. на странице selection_sort.html.

Реализация алгоритма

public static void selection(int[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        // i 代表每轮选择最小元素要交换到的目标索引
        int s = i; // 代表最小元素的索引
        for (int j = s + 1; j < a.length; j++) {
            if (a[s] > a[j]) { // j 元素比 s 元素还要小, 更新 s
                s = j;
            }
        }
        if (s != i) {
            swap(a, s, i);
        }
        System.out.println(Arrays.toString(a));
    }
}
  • Точка оптимизации: чтобы уменьшить количество обменов, вы можете сначала найти наименьший индекс в каждом раунде, а затем обменивать элементы в конце каждого раунда.

Сравните с пузырьковой сортировкой

  1. Средняя временная сложность обоих
  2. Сортировка выбором, как правило, быстрее, чем всплывающая, потому что в ней меньше обменов.
  3. Но если коллекция сильно упорядочена, всплытие лучше, чем выборка.
  4. Пузырь — устойчивый алгоритм сортировки, а выбор — неустойчивый алгоритм сортировки.
    • Стабильная сортировка — это многократная сортировка по разным полям в объекте без нарушения порядка элементов с одинаковым значением.
    • Противоположное верно для нестабильной сортировки

Стабильная сортировка и нестабильная сортировка

System.out.println("=================不稳定================");
Card[] cards = getStaticCards();
System.out.println(Arrays.toString(cards));
selection(cards, Comparator.comparingInt((Card a) -> a.sharpOrder).reversed());
System.out.println(Arrays.toString(cards));
selection(cards, Comparator.comparingInt((Card a) -> a.numberOrder).reversed());
System.out.println(Arrays.toString(cards));

System.out.println("=================稳定=================");
cards = getStaticCards();
System.out.println(Arrays.toString(cards));
bubble(cards, Comparator.comparingInt((Card a) -> a.sharpOrder).reversed());
System.out.println(Arrays.toString(cards));
bubble(cards, Comparator.comparingInt((Card a) -> a.numberOrder).reversed());
System.out.println(Arrays.toString(cards));

Все они отсортированы сначала по масти (♠♥♣♦), а затем по номеру (AKQJ…)

  • Когда неустойчивый алгоритм сортировки сортирует по номерам, он нарушит порядок мастей с одинаковым значением
[[♠7], [♠2], [♠4], [♠5], [♥2], [♥5]]
[[♠7], [♠5], [♥5], [♠4], [♥2], [♠2]]

Получается, что ♠2 впереди, а ♥2 сзади, и они переставлены по номерам, и их позиции изменились.

  • Когда алгоритм стабильной сортировки сортирует по числам, он сохраняет исходный порядок масти того же значения, как показано ниже. Относительное положение ♠2 и ♥2 остается неизменным.
[[♠7], [♠2], [♠4], [♠5], [♥2], [♥5]]
[[♠7], [♠5], [♥5], [♠4], [♠2], [♥2]]

4. Сортировка вставками

Требовать

  • Уметь описать алгоритм сортировки вставками на своем родном языке
  • Возможность сравнения сортировки вставками с сортировкой выбором

Алгоритм Описание

  1. Разделите массив на две области, отсортированную область и несортированную область, каждый раунд берет первый элемент из несортированной области и вставляет его в отсортированную область (порядок должен быть гарантирован)
  2. Повторяйте вышеуказанные шаги, пока весь массив не будет отсортирован.

Более наглядное описание см. на странице insertion_sort.html.

Реализация алгоритма

// 修改了代码与希尔排序一致
public static void insert(int[] a) {
    // i 代表待插入元素的索引
    for (int i = 1; i < a.length; i++) {
        int t = a[i]; // 代表待插入的元素值
        int j = i;
        System.out.println(j);
        while (j >= 1) {
            if (t < a[j - 1]) { // j-1 是上一个元素索引,如果 > t,后移
                a[j] = a[j - 1];
                j--;
            } else { // 如果 j-1 已经 <= t, 则 j 就是插入位置
                break;
            }
        }
        a[j] = t;
        System.out.println(Arrays.toString(a) + " " + j);
    }
}

Сравните с сортировкой выбором

  1. Средняя временная сложность обоих
  2. Вставка немного лучше, чем выбор в большинстве случаев
  3. Временная сложность вставки отсортированного набора
  4. Вставка — устойчивый алгоритм сортировки, а выборка — неустойчивый алгоритм сортировки.

намекать

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

5. Сортировка по холму

Требовать

  • Уметь описать алгоритм сортировки Хилла на своем родном языке

Алгоритм Описание

  1. Сначала выберите последовательность пробелов, например (n/2, n/4... 1), n ​​— длина массива
  2. В каждом раунде элементы с равными промежутками рассматриваются как группа, а элементы в группе вставляются и сортируются для двух целей ① Скорость вставки и сортировки
    небольшого количества элементов очень высока
    ② Пусть элементы с большие значения в группе перемещаются назад быстрее
  3. Когда разрыв постепенно уменьшается до 1, сортировка может быть завершена.

Для более яркого описания, пожалуйста, обратитесь к: shell_sort.html

Реализация алгоритма

private static void shell(int[] a) {
    int n = a.length;
    for (int gap = n / 2; gap > 0; gap /= 2) {
        // i 代表待插入元素的索引
        for (int i = gap; i < n; i++) {
            int t = a[i]; // 代表待插入的元素值
            int j = i;
            while (j >= gap) {
                // 每次与上一个间隙为 gap 的元素进行插入排序
                if (t < a[j - gap]) { // j-gap 是上一个元素索引,如果 > t,后移
                    a[j] = a[j - gap];
                    j -= gap;
                } else { // 如果 j-1 已经 <= t, 则 j 就是插入位置
                    break;
                }
            }
            a[j] = t;
            System.out.println(Arrays.toString(a) + " gap:" + gap);
        }
    }
}

Рекомендации

6. Быстрая сортировка

Требовать

  • Уметь описать алгоритм быстрой сортировки на своем родном языке
  • Освойте один из рукописных кодов односторонней петли и двусторонней петли
  • Уметь объяснять особенности быстрой сортировки
  • Понять сравнение производительности схем секционирования Lomuto и Hall

Алгоритм Описание

  1. Каждый раунд сортировки выбирает опорную точку (стержень) для разделения.
    1. Пусть элементы меньше точки отсчета входят в один раздел, а элементы больше точки отсчета входят в другой раздел
    2. Когда разбиение завершено, положение поворотного элемента является его конечным положением.
  1. Повторяйте описанный выше процесс в подразделе до тех пор, пока количество элементов подраздела не станет меньше или равно 1, что отражает идею «разделяй и властвуй».
  2. Как видно из приведенного выше описания, ключ лежит в алгоритме разбиения, общие включают схему разбиения Ломуто, схему разбиения двустороннего цикла и схему разбиения Холла.

Более наглядное описание см. на странице quick_sort.html.

Односторонняя циклическая быстрая сортировка (схема ломуто-разбиения)

  1. Выберите самый правый элемент в качестве базового элемента
  2. Указатель j отвечает за поиск элемента, меньшего, чем опорная точка, и, как только он найден, он обменивается с i
  3. Указатель i поддерживает границы элементов меньше контрольной точки, а также является целевым индексом для каждого обмена
  4. Наконец, контрольная точка заменяется на i, и i является позицией раздела.
public static void quick(int[] a, int l, int h) {
    if (l >= h) {
        return;
    }
    int p = partition(a, l, h); // p 索引值
    quick(a, l, p - 1); // 左边分区的范围确定
    quick(a, p + 1, h); // 左边分区的范围确定
}

private static int partition(int[] a, int l, int h) {
    int pv = a[h]; // 基准点元素
    int i = l;
    for (int j = l; j < h; j++) {
        if (a[j] < pv) {
            if (i != j) {
                swap(a, i, j);
            }
            i++;
        }
    }
    if (i != h) {
        swap(a, h, i);
    }
    System.out.println(Arrays.toString(a) + " i=" + i);
    // 返回值代表了基准点元素所在的正确索引,用它确定下一轮分区的边界
    return i;
}

Двусторонняя циклическая быстрая сортировка (не совсем эквивалентная схеме разделения Хора-Холла)

  1. Выберите крайний левый элемент в качестве базового элемента
  2. Указатель j отвечает за поиск элементов, меньших, чем контрольная точка, справа налево, а указатель i отвечает за поиск элементов, больших, чем контрольная точка, слева направо После того, как они найдены, они обмениваются до тех пор, пока i, j не пересекутся.
  3. Наконец, контрольная точка заменяется на i (в это время i и j равны), и i является позицией раздела

Основные моменты

  1. Контрольная точка находится слева, а за j должно следовать i
  2. в то время как ( i < j && a[j] > pv ) j–
  3. в то время как ( я < j && a [i] <= pv ) я ++
private static void quick(int[] a, int l, int h) {
    if (l >= h) {
        return;
    }
    int p = partition(a, l, h);
    quick(a, l, p - 1);
    quick(a, p + 1, h);
}

private static int partition(int[] a, int l, int h) {
    int pv = a[l];
    int i = l;
    int j = h;
    while (i < j) {
        // j 从右找小的
        while (i < j && a[j] > pv) {
            j--;
        }
        // i 从左找大的
        while (i < j && a[i] <= pv) {
            i++;
        }
        swap(a, i, j);
    }
    swap(a, l, j);
    System.out.println(Arrays.toString(a) + " j=" + j);
    return j;
}

Функции быстрой сортировки

  1. Средняя временная сложность , наихудшая временная сложность
  2. Когда объем данных большой, преимущество очень очевидно
  3. нестабильный сорт

Схема зонирования Ломуто против схемы зонирования зала

Дополнительный код Описание

  • day01.sort.QuickSort3 демонстрирует двухстороннюю быструю сортировку с улучшенными свойствами кавитации с меньшим количеством сравнений
  • day01.sort.QuickSortHoare демонстрирует реализацию разделения Хоара
  • Day01.sort.LomutoVsHoare сравнивает количество ходов, выполненных четырьмя разделами.

7. Список массивов

Требовать

  • Основные правила расширения ArrayList

Правила расширения

  1. ArrayList() будет использовать массив нулевой длины
  2. ArrayList(int initialCapacity) будет использовать массив с указанной емкостью
  3. public ArrayList(Collection<? extends E> c) будет использовать размер c в качестве емкости массива
  4. add(Object o) впервые увеличивает емкость до 10 и снова увеличивает емкость в 1,5 раза по сравнению с предыдущей емкостью.
  5. addAll(Collection c) Когда нет элементов, расширяется до Math.max(10, фактическое количество элементов), а когда есть элементы, становится Math.max(в 1,5 раза больше исходной емкости, фактическое количество элементов)

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

намекать

  • См. тестовый код day01.list.TestArrayList, его здесь не будет
  • Следует отметить , что в примере используется отражение для более интуитивного отражения характеристик расширения ArrayList, но начиная с JDK 9 из-за влияния модульности на отражение больше ограничений, и необходимо добавлять параметры ВМ при запуске тестовый код для --add-opens java.base/java.util=ALL-UNNAMEDзапуска Пройдено, все следующие примеры имеют одну и ту же проблему

описание кода

  • day01.list.TestArrayList#arrayListGrowRule демонстрирует правила расширения метода add(Object), а входной параметр n представляет, сколько раз печатать длину расширенного массива.

8. Итератор

Требовать

  • Узнайте, что такое Fail-Fast и Fail-Safe

Отказоустойчивость и отказоустойчивость

  • ArrayList — типичный представитель отказоустойчивости, он не может быть изменен при обходе и дает сбой при первой возможности.
  • CopyOnWriteArrayList — типичный представитель отказоустойчивости, его можно модифицировать при обходе, принцип — разделение чтения-записи.

намекать

  • См. тестовый код day01.list.FailFastVsFailSafe, его здесь не будет

9. Связанный список

Требовать

  • Уметь четко объяснить разницу между LinkedList и ArrayList и обратить внимание на исправление некоторых ошибок

Связанный список

  1. На основе двусвязного списка не требуется непрерывная память
  2. Произвольный доступ медленный (обход по связанному списку)
  3. Вставка и удаление «голова к хвосту» с высокой производительностью
  4. Занимает много памяти

ArrayList

  1. На основе массива, требует непрерывной памяти
  2. Быстрый произвольный доступ (имеется в виду доступ по индексу)
  3. Производительность вставки и удаления хвоста в порядке, но другие части вставки и удаления будут перемещать данные, поэтому производительность будет низкой.
  4. Может использовать кеш процессора, принцип локальности

описание кода

  • day01.list.ArrayListVsLinkedList#randomAccess и производительность произвольного доступа
  • day01.list.ArrayListVsLinkedList#addMiddle сравнивает производительность вставки в середину
  • day01.list.ArrayListVsLinkedList#addFirst сравнение производительности вставки заголовков
  • day01.list.ArrayListVsLinkedList#addLast и производительность вставки хвоста
  • day01.list.ArrayListVsLinkedList#linkedListSize вывести LinkedList, занимающий память
  • day01.list.ArrayListVsLinkedList#arrayListSize Печать ArrayList, занимающего память

10. Хэш-карта

Требовать

  • Освойте базовую структуру данных HashMap
  • Мастер-дерево
  • Понимать метод расчета индекса, значение вторичного хэша и влияние емкости на расчет индекса.
  • Освойте процесс пут, расширение и коэффициент расширения
  • Понимать проблемы, которые могут быть вызваны параллельным использованием HashMap
  • Разобраться с дизайном ключей

1) Базовая структура данных

  • 1.7 Массив + связанный список
  • 1.8 Массив+ (связный список | красно-черное дерево)

Для более наглядной демонстрации см. hash-demo.jar в данных.Для операции требуется среда jdk14 или выше.Войдите в каталог пакета jar и выполните следующую команду

java -jar --add-exports java.base/jdk.internal.misc=ALL-UNNAMED hash-demo.jar

2) Арборизация и деградация

значение дерева

  • Красно-черное дерево используется, чтобы избежать DoS-атак и предотвратить снижение производительности, когда связанный список слишком длинный.Дерево должно быть случайной ситуацией, и это стратегия практических результатов.
  • Временная сложность поиска и обновления хеш-таблицы составляет , а временная сложность поиска и обновления красно-черного дерева составляет , а TreeNode занимает больше места, чем обычный Node.Если это не нужно, попробуйте использовать связанный список
  • Если хеш-значение достаточно случайное, то оно будет распределено по Пуассону в хеш-таблице.В случае коэффициента загрузки 0,75 вероятность связанного списка длиной более 8 составляет 0,00000006. Порог древовидности выбирается равным быть 8, чтобы сделать вероятность дерева достаточно малой

правила дерева

  • Когда длина связанного списка превышает порог дерева 8, сначала попробуйте расширить емкость, чтобы уменьшить длину связанного списка.Если емкость массива> = 64, будет выполнено дерево.

вырожденные правила

  • Случай 1: если количество элементов дерева <= 6 при разбиении дерева во время расширения емкости, связанный список будет вырожденным.
  • Случай 2: при удалении узлов дерева, если один из root, root.left, root.right, root.left.left равен нулю, он выродится в связанный список.

3) Расчет индекса

Метод расчета индекса

  • Сначала вычислите hashCode() объекта
  • Затем вызовите метод hash() HashMap для вторичного хеширования.
    • Второй метод hash() предназначен для синтеза высокоуровневых данных и более равномерного распределения хэшей.
  • Наконец & (capacity – 1) получает индекс

Почему емкость массива равна энной степени 2

  1. Это более эффективно при вычислении индекса: если это n-я степень числа 2, вы можете использовать побитовую операцию И вместо по модулю.
  2. При расширении индекс эффективнее пересчитывать: элемент с хэшем и oldCap == 0 остается в исходной позиции, иначе новая позиция = старая позиция + oldCap

Уведомление

  • Второй хеш должен соответствовать предпосылке проекта, согласно которой емкость равна n-й степени числа 2. Если емкость хэш-таблицы не является n-й степенью числа 2, второй хеш не нужен.
  • Емкость равна n-й степени числа 2. Этот дизайн более эффективен при вычислении индексов, но дисперсия хэша не очень хороша, и в качестве компенсации необходим второй хэш. Типичным примером, который не использует этот дизайн, является Hashtable.

4) поставить и расширить

положить процесс

  1. HashMap создает массив лениво, и массив создается только при первом использовании
  2. Вычислить индекс (индекс корзины)
  3. Если индекс ведра еще не занят, создайте заполнитель Node и верните
  4. Если индекс ведра уже занят
    1. Уже популярная логика добавления или обновления черного дерева TreeNode.
    2. Это обычный узел, и соблюдается логика добавления или обновления связанного списка.Если длина связанного списка превышает пороговое значение дерева, соблюдается логика дерева.
  1. Перед возвратом проверьте, превышает ли емкость пороговое значение, и увеличьте емкость, как только она превысит

Разница между 1.7 и 1.8

  1. При вставке узла в связанный список 1.7 — метод вставки начала, а 1.8 — метод вставки хвоста.
  2. 1,7 больше или равно порогу и нет места для расширения емкости, а 1,8 больше порога для расширения емкости
  3. 1.8 При расширении и расчете индекса узла он будет оптимизирован

Почему коэффициент расширения (нагрузки) по умолчанию равен 0,75f

  1. Хороший компромисс между использованием пространства и временем запроса
  2. Если оно больше этого значения, место будет сохранено, но связанный список будет длиннее и повлияет на производительность.
  3. Если оно меньше этого значения, конфликты уменьшаются, но расширение емкости будет происходить чаще, а пространство будет больше заниматься.

5) Проблемы параллелизма

Мертвая цепь расширения (будет существовать в версии 1.7)

1.7 Исходный код выглядит следующим образом:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
  • И e, и next являются локальными переменными, используемыми для указания на текущий узел и следующий узел.
  • Временные переменные e и next потока 1 (зеленый) только что ссылались на эти два узла, и прежде чем узел можно будет переместить в будущем, происходит переключение потока, и поток 2 (синий) завершает расширение и миграцию.

  • Завершено расширение потока 2. Из-за метода вставки заголовка порядок связанного списка обратный. Однако временные переменные e и next потока 1 по-прежнему относятся к этим двум узлам, поэтому требуется еще одна миграция.

  • первый цикл
    • Цикл выполняется до переключения потока, обратите внимание, что в это время e указывает на узел a, а затем указывает на узел b.
    • Вставьте узел в голову е. Обратите внимание, что на картинке две копии узла, а на самом деле только одна (две копии нарисованы для того, чтобы стрелку не нацарапать)
    • Когда цикл закончится, e укажет на следующий, который является узлом b

  • второй цикл
    • следующий указывает на узел a
    • e точка вставки заголовка b
    • Когда цикл заканчивается, e указывает на следующий, который является узлом a

[Не удалось передать изображение по внешней ссылке, на исходном сайте может быть механизм защиты от пиявки, рекомендуется сохранить изображение и загрузить его напрямую (img-A3enIgab-1691920287269)()]

  • третий цикл
    • следующий указывает на ноль
    • Заголовок e вставляет узел a, следующий из a указывает на b (ранее a.next всегда был нулевым), следующий из b указывает на a, и неработающая ссылка стала
    • Когда цикл заканчивается, e указывает на следующий, который равен нулю, поэтому он нормально выйдет в четвертом цикле.

Неупорядоченность данных (1.7, 1.8 будут существовать)

  • Справочник по коду day01.map.HashMapMissData, конкретные шаги по отладке см. в видео

Дополнительный код Описание

  • day01.map.HashMapDistribution демонстрирует, что длина связанного списка на карте соответствует распределению Пуассона.
  • day01.map.DistributionAffectedByCapacity демонстрирует влияние емкости и значения hashCode на распределение
    • day01.map.DistributionAffectedByCapacity#hashtableGrowRule демонстрирует правило расширения Hashtable
    • day01.sort.Utils#randomArray Если hashCode достаточно случайный, значение емкости равное 2 в степени n не имеет большого значения.
    • day01.sort.Utils#lowSameArray Если hashCode имеет одинаковое количество младших бит, емкость равна 2 в n-й степени, что приведет к неравномерному распределению
    • day01.sort.Utils#evenArray Если имеется много четных чисел hashCode и емкость равна 2 в энной степени, распределение будет неравномерным.
    • Из этого можно сделать вывод, что второй хэш очень важен для конструкции, емкость которой равна n-й степени числа 2.
  • day01.map.HashMapVsHashtable демонстрирует разницу в распределении HashMap и Hashtable для одного и того же количества строк слов.

6) Ключевой дизайн

основные требования к дизайну

  1. Ключ HashMap может быть нулевым, но в других реализациях Map это не так.
  2. В качестве ключевого объекта должны быть реализованы hashCode и equals, а содержимое ключа не может быть изменено (неизменяемое).
  3. Хэш-код ключа должен иметь хорошую хэшируемость.

Если ключ является переменным, например, если возраст изменен, он не будет запрашиваться при повторном запросе.

public class HashMapMutableKey {
    public static void main(String[] args) {
        HashMap<Student, Object> map = new HashMap<>();
        Student stu = new Student("张三", 18);
        map.put(stu, new Object());

        System.out.println(map.get(stu));

        stu.age = 19;
        System.out.println(map.get(stu));
    }

    static class Student {
        String name;
        int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && Objects.equals(name, student.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    }
}

hashCode() дизайн объекта String

  • Цель состоит в том, чтобы добиться относительно однородного хеш-эффекта, а хеш-код каждой строки достаточно уникален.
  • Каждый символ в строке может быть выражен как число, называемое , где диапазон i равен 0 ~ n - 1.
  • Формула хеша:
  • Формула подстановки 31 имеет лучшие хэш-свойства, а 31*h можно оптимизировать как
    • То есть $32 ∗h -h $
    • Прямо сейчас
    • Прямо сейчас

11. Одноэлементный паттерн

Требовать

  • Освойте реализацию пяти одноэлементных паттернов
  • Понять, почему DCL реализует volatile для изменения статических переменных.
  • Понимать сценарии, в которых синглтоны используются в jdk.

Голодный китайский стиль

public class Singleton1 implements Serializable {
    private Singleton1() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }

    private static final Singleton1 INSTANCE = new Singleton1();

    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

    public Object readResolve() {
        return INSTANCE;
    }
}
  • Конструктор выдает исключение, чтобы отражение не разрушило синглтон.
  • readResolve()заключается в том, чтобы предотвратить разрушение синглтона десериализацией

перечислить китайский стиль

public enum Singleton2 {
    INSTANCE;

    private Singleton2() {
        System.out.println("private Singleton2()");
    }

    @Override
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}
  • Перечисление может естественным образом предотвратить отражение и десериализацию от уничтожения синглетонов.

Ленивый

public class Singleton3 implements Serializable {
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    private static Singleton3 INSTANCE = null;

    // Singleton3.class
    public static synchronized Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

}
  • На самом деле синхронизация требуется только при первом создании одноэлементного объекта, но на самом деле код синхронизируется каждый раз, когда он вызывается.
  • Итак, со следующим улучшением блокировки двойной проверки

Двойная проверка блокировки в ленивом стиле

public class Singleton4 implements Serializable {
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    private static volatile Singleton4 INSTANCE = null; // 可见性,有序性

    public static Singleton4 getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton4.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton4();
                }
            }
        }
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

Почему необходимо добавить volatile:

  • INSTANCE = new Singleton4()Не атомарный, разделенный на 3 шага: создать объект, вызвать конструктор и присвоить значение статической переменной.Последние два шага могут быть оптимизированы путем переупорядочения инструкций и стать сначала присваиванием, а затем вызовом конструктора
  • Если поток 1 выполняет присваивание первым, а поток 2 INSTANCE == nullобнаруживает, что INSTANCE не равен нулю, когда он достигает первого, он вернет не полностью построенный объект.

ленивый внутренний класс

public class Singleton5 implements Serializable {
    private Singleton5() {
        System.out.println("private Singleton5()");
    }

    private static class Holder {
        static Singleton5 INSTANCE = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Holder.INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}
  • Избегайте недостатков блокировки с двойной проверкой

Воплощение синглтона в JDK

  • Среда выполнения воплощает голодный китайский синглтон
  • Консоль воплощает ленивый синглтон с двойной проверкой блокировки
  • Ленивый синглтон внутреннего класса EmptyNavigableSet в коллекциях
  • Ленивый синглтон внутреннего класса ReverseComparator.REVERSE_ORDER
  • Comparators.NaturalOrderComparator.INSTANCE перечисляет синглтоны в китайском стиле

параллельные статьи

1. Состояние потока

Требовать

  • Овладейте шестью состояниями потоков Java
  • Освоение переходов между состояниями потока Java
  • Может понять разницу между пятью состояниями и шестью состояниями

Шесть состояний и переходов

соответственно

  • новая сборка
    • Когда объект потока создан, но метод запуска не был вызван, он находится в новом состоянии .
    • В настоящее время не связан с базовым потоком операционной системы.
  • работоспособный
    • После вызова метода start он войдет в runnable из только что созданного
    • В настоящее время он связан с базовым потоком и запланирован для выполнения операционной системой.
  • конец
    • Код в потоке был выполнен и входит в финализацию из runnable
    • В это время связь с базовым потоком будет отменена.
  • блокировать
    • Когда получение блокировки не удается, блокируется очередь блокировки , которая может запускаться в монитор , и в это время процессорное время не занято.
    • Когда удерживающий блокировку поток освобождает блокировку, он пробуждает заблокированный поток в очереди блокировки в соответствии с определенными правилами, и пробужденный поток переходит в состояние выполнения.
  • ждать
    • Когда блокировка получена успешно, но поскольку условие не выполнено, вызывается метод wait().В это время блокировка освобождается от рабочего состояния и входит в состояние ожидания монитора, установленное на ожидание , которое также не занимает процессор. время.
    • Когда другие потоки, удерживающие блокировку, вызывают метод notify() или notifyAll(), ожидающие потоки в ожидающем наборе будут разбужены в соответствии с определенными правилами и восстановлены в работоспособном состоянии .
  • срок ожидания
    • Когда блокировка получена успешно, но поскольку условие не выполнено, вызывается метод ожидания (длительный).В это время блокировка освобождается от рабочего состояния и переходит в режим ожидания монитора для ограниченного по времени ожидания , который также не занимает процессорное время.
    • Когда другие потоки, удерживающие блокировку, вызывают метод notify() или notifyAll(), ожидающие потоки с ограниченным временем ожидания в наборе ожидающих будут пробуждены в соответствии с определенными правилами , восстановлены в работоспособном состоянии и повторно конкурируют за блокировку.
    • Если время ожидания истекло, он также восстановится из состояния ожидания с ограничением по времени в состояние готовности к выполнению и повторно соревнуется за блокировку.
    • Другая ситуация заключается в том, что вызов метода sleep(long) также перейдет в состояние ожидания с ограничением по времени из состояния runnable , но это не имеет ничего общего с монитором и не требует активного пробуждения. он естественным образом вернется в работоспособное состояние

Другие ситуации (просто нужно знать)

  • Вы можете использовать метод interrupt() для прерывания ожидающих , ограниченных по времени ожидающих потоков и восстановления их в работоспособное состояние .
  • Park, unpark и другие методы также могут заставить потоки ждать и просыпаться.

пять штатов

Заявление о пяти состояниях исходит из разделения на уровне операционной системы.

  • Состояние выполнения: выделяется процессорному времени, может выполнять код в потоке.
  • Состояние готовности: подходит для процессорного времени, но еще не настала очередь
  • Заблокированное состояние: не подходит для процессорного времени
    • Охватывает блокировку , ожидание , ожидание по времени, упомянутое в состоянии Java.
    • Существует больше блокирующего ввода-вывода, что означает, что когда поток вызывает блокирующий ввод-вывод, фактическая работа завершается устройством ввода-вывода.В это время потоку нечего делать и он может только ждать.
  • Новое и конечное состояние: аналогично состоянию с тем же именем в java, больше не многословно

2. Пул потоков

Требовать

  • Освойте 7 основных параметров пула потоков

Семь параметров

  1. corePoolSize количество основных потоков — максимальное количество потоков, которые будут храниться в пуле
  2. maxPoolSize максимальное количество потоков - максимальное количество основных потоков + спасательных потоков
  3. keepAliveTime Survival time - время выживания спасательного потока, если в течение времени выживания нет новых задач, этот ресурс потока будет освобожден
  4. unit time unit - единица времени выживания аварийного потока, например секунды, миллисекунды и т.д.
  5. workQueue — когда нет свободных основных потоков, новые задачи будут поставлены в очередь в этой очереди, а когда очередь будет заполнена, будут созданы аварийные потоки для выполнения задач.
  6. Фабрика потоков threadFactory — вы можете настроить создание объектов потока, например, установить имя потока, является ли он потоком демона и т. д.
  7. стратегия отклонения обработчика — когда все потоки заняты и рабочая очередь заполнена, будет запущена стратегия отклонения
    1. Выдать исключение java.util.concurrent.ThreadPoolExecutor.AbortPolicy
    2. Задачи выполняются вызывающей стороной java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy.
    3. Отменить задачи java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
    4. Отбрасывать самые старые задачи в очереди java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy

[Не удалось передать изображение по внешней ссылке, исходный сайт может иметь механизм защиты от пиявки, рекомендуется сохранить изображение и загрузить его напрямую (img-U9e6Mevj-1691920287271)()]

описание кода

day02.TestThreadPoolExecutor более наглядно демонстрирует основной состав пула потоков.

3. ждать или спать

Требовать

  • уметь различать

Одна общность, три отличия

точки соприкосновения

  • Эффекты wait(), wait(long) и sleep(long) заключаются в том, что текущий поток временно отказывается от права использовать ЦП и переходит в состояние блокировки.

разница

  • Метод атрибуции отличается
    • sleep(long) — это статический метод Thread
    • И wait(), wait(long) — все методы-члены Object, каждый объект имеет
  • Просыпайтесь в разное время
    • Потоки, выполняющие sleep(long) и wait(long), проснутся после ожидания соответствующих миллисекунд.
    • wait(long) и wait() также могут быть разбужены уведомлением, если wait() не проснется, оно будет ждать вечно
    • Их всех можно прервать, чтобы проснуться
  • Различные характеристики замка (акцент)
    • Вызов метода ожидания должен сначала получить блокировку объекта ожидания, в то время как сон не имеет такого ограничения.
    • После выполнения метода ожидания блокировка объекта будет снята, что позволит другим потокам получить блокировку объекта (я отказываюсь от процессора, но вы все равно можете его использовать)
    • И если сон выполняется в синхронизированном блоке кода, он не снимет объектную блокировку (я отказываюсь от процессора, и вы не можете его использовать)

4. блокировка против синхронизации

Требовать

  • Освойте разницу между блокировкой и синхронизацией
  • Понимание честных и нечестных блокировок ReentrantLock
  • Понимание условных переменных в ReentrantLock

три уровня

разница

  • грамматический уровень
    • synchronized — это ключевое слово, исходный код в jvm, реализованный на языке c++
    • Lock — это интерфейс, исходный код предоставлен jdk и реализован на языке java.
    • При использовании синхронизированного блокировка блока кода выхода синхронизации будет снята автоматически, но при использовании блокировки вам необходимо вручную вызвать метод разблокировки, чтобы снять блокировку
  • функциональный уровень
    • Обе относятся к пессимистическим блокировкам, и обе имеют базовые функции взаимного исключения, синхронизации и повторного входа в блокировку.
    • Блокировка предоставляет множество функций, которых нет у synchronized, например, получение состояния ожидания, справедливая блокировка, прерывание, тайм-аут и несколько переменных условий.
    • Блокировка имеет реализации, подходящие для разных сценариев, такие как ReentrantLock, ReentrantReadWriteLock.
  • уровень исполнения
    • Когда нет конкуренции, синхронизированный сделал много оптимизаций, таких как предвзятые блокировки, облегченные блокировки, и производительность неплохая.
    • Реализации блокировки обычно обеспечивают лучшую производительность при высокой конкуренции.

честный замок

  • Честное воплощение честного замка
    • Потоки , уже находящиеся в очереди на блокировку (независимо от тайм-аута), всегда выполняются по принципу «первым пришел — первым вышел».
    • Справедливая блокировка относится к потокам , которые не находятся в очереди блокировки, чтобы конкурировать за блокировку.Если очередь не пуста, честно дождаться конца очереди
    • Несправедливая блокировка означает, что потоки , не находящиеся в очереди блокировки, конкурируют за блокировку и конкурируют с потоком, пробужденным головой очереди.
  • Честные блокировки снижают пропускную способность и обычно не используются.

переменная условия

  • Функция переменной условия в ReentrantLock аналогична обычному синхронизированному ожиданию и уведомлению, которое используется в структуре связанного списка для временного ожидания, когда поток получает блокировку и обнаруживает, что условие не выполняется.
  • Отличие от синхронизированного набора ожидания заключается в том, что в ReentrantLock может быть несколько условных переменных, что позволяет добиться более точного управления ожиданием и пробуждением.

описание кода

  • day02.TestReentrantLock более наглядно демонстрирует внутреннюю структуру ReentrantLock

5. летучий

Требовать

  • Три вопроса, которые следует учитывать при освоении безопасности потоков
  • Какие проблемы можно решить, освоив volatile

атомарность

  • Причина: при многопоточности инструкции разных потоков чередуются, что приводит к путанице при чтении и записи общих переменных.
  • Решение: используйте пессимистичные блокировки или оптимистичные блокировки, volatile не может решить проблему атомарности.

видимость

  • Причина: изменения общих переменных из-за оптимизации компилятора, оптимизации кэша или оптимизации переупорядочения инструкций ЦП, которые не видны другим потокам.
  • Решение: украшение общих переменных с помощью volatile может предотвратить выполнение оптимизаций, таких как компиляторы, и сделать изменение общих переменных одним потоком видимым для другого потока.

упорядоченность

  • Причина: из-за оптимизации компилятора, оптимизации кэша или оптимизации переупорядочения инструкций ЦП фактический порядок выполнения инструкций не соответствует порядку записи.
  • Решение: изменение общих переменных с помощью volatile добавит различные барьеры при чтении и записи общих переменных, предотвращая пересечение барьеров другими операциями чтения и записи, тем самым достигая эффекта предотвращения переупорядочения.
  • Уведомление:
    • Барьер , добавляемый переменной volatile , предназначен для предотвращения постановки в очередь других операций записи над барьером под переменной volatile.
    • Барьер для чтения энергозависимой переменной заключается в том, чтобы другие операции чтения ниже не пересекали барьер и не ранжировались выше чтения энергозависимой переменной.
    • Барьеры, добавляемые непостоянными операциями чтения и записи, могут предотвратить изменение порядка инструкций только в одном потоке.

описание кода

  • day02.threadsafe.AddAndSubtract демонстрирует атомарность
  • Day02.threadsafe.ForeverLoop видимость демонстрации
    • Примечание. Было доказано, что этот пример является проблемой видимости, вызванной оптимизацией компилятора.
  • day02.threadsafe.Reordering демонстрирует порядок
    • Его нужно упаковать в баночку и протестировать
  • Пожалуйста, также обратитесь к пояснению к видео

6. Пессимистическая блокировка против оптимистичной блокировки

Требовать

  • Освойте разницу между пессимистичной блокировкой и оптимистичной блокировкой.

Сравнение пессимистической блокировки и оптимистичной блокировки

  • Представителями пессимистичных замков являются синхронизированные и замковые замки.
    • Основная идея заключается в следующем: [Потоки могут оперировать общими переменными только в том случае, если они владеют блокировкой. Только один поток может успешно занимать блокировку каждый раз, и поток, который не может получить блокировку, должен остановиться и ждать]
    • Поток от запуска до блокировки, а затем от блокировки до пробуждения включает в себя переключение контекста потока. Если это происходит часто, это влияет на производительность.
    • На самом деле, когда поток получает синхронизацию и блокировку блокировки, если блокировка уже занята, он делает несколько повторных попыток, чтобы уменьшить вероятность блокировки.
  • Представителем оптимистичной блокировки является AtomicInteger, который использует cas для обеспечения атомарности.
    • Его основная идея заключается в том, что [нет необходимости в блокировке, только один поток может каждый раз успешно изменять общую переменную, другие потоки, потерпевшие неудачу, не должны останавливаться, продолжайте повторять попытки до тех пор, пока они не будут успешными]
    • Поскольку поток всегда выполняется, нет необходимости блокировать его, поэтому переключение контекста потока не требуется.
    • Требуется поддержка многоядерных процессоров, а количество потоков не должно превышать количество ядер процессора.

описание кода

  • day02.SyncVsCas демонстрирует использование оптимистичных и пессимистичных блокировок для решения атомарных задач.
  • Пожалуйста, также обратитесь к пояснению к видео

7. Hashtable против ConcurrentHashMap

Требовать

  • Освойте разницу между Hashtable и ConcurrentHashMap
  • Изучите различия в реализации ConcurrentHashMap в разных версиях.

Для более наглядной демонстрации см. hash-demo.jar в данных.Для операции требуется среда jdk14 или выше.Войдите в каталог пакета jar и выполните следующую команду

java -jar --add-exports java.base/jdk.internal.misc=ALL-UNNAMED hash-demo.jar

Hashtable против ConcurrentHashMap

  • И Hashtable, и ConcurrentHashMap являются потокобезопасными коллекциями карт.
  • Hashtable имеет низкий уровень параллелизма, вся Hashtable соответствует блокировке, и одновременно с ней может работать только один поток.
  • ConcurrentHashMap имеет высокий уровень параллелизма, и вся ConcurrentHashMap соответствует нескольким блокировкам.Пока потоки обращаются к разным блокировкам, конфликтов не будет.

ConcurrentHashMap 1.7

  • Структура данных: Segment(大数组) + HashEntry(小数组) + 链表каждый сегмент соответствует блокировке, если несколько потоков обращаются к разным сегментам, конфликта не будет.
  • Параллелизм: размер массива Segment — это параллелизм, который определяет, сколько потоков могут получить одновременный доступ. Массив Segment не может быть расширен, что означает, что параллелизм фиксируется при создании ConcurrentHashMap.
  • расчет индекса
    • Предполагая, что длина большого массива равна , индекс ключа в большом массиве равен старшим m битам вторичного хеш-значения ключа.
    • Предполагая, что длина маленького массива равна , индекс ключа в маленьком массиве равен младшим n битам вторичного хеш-значения ключа.
  • Расширение: расширение каждого небольшого массива относительно независимое.Когда небольшой массив превышает коэффициент расширения, расширение будет запущено, и расширение будет удваиваться каждый раз.
  • Прототип Segment[0]: При создании других небольших массивов в первый раз, этот прототип будет использоваться в качестве основы, длина массива и коэффициент расширения будут основаны на прототипе.

ConcurrentHashMap 1.8

  • Структура данных: Node 数组 + 链表或红黑树каждый головной узел массива используется как замок, если головные узлы, к которым обращаются несколько потоков, различны, конфликта не будет. Если конкуренция возникает при создании головного узла в первый раз, используйте cas вместо synchronized для дальнейшего повышения производительности.
  • Параллелизм: размер массива узлов такой же, как и параллелизм.В отличие от 1.7, массив узлов можно расширить.
  • Условие расширения: когда массив узлов заполнен на 3/4, емкость будет расширена.
  • Блок расширения: используйте связанный список как единицу для переноса связанного списка с заднего на передний.После завершения переноса замените старый головной узел массива на ForwardingNode.
  • Параллельное получение во время расширения
    • В зависимости от того, решает ли ForwardingNode поиск в новом массиве или в старом массиве, он не будет блокироваться.
    • Если длина связанного списка превышает 1, нужно скопировать узел (создать новый узел), опасаясь, что следующий указатель изменится после переноса узла
    • Если после расширения индекс нескольких последних элементов связанного списка остается неизменным, узел копировать не нужно.
  • Параллельная установка во время расширения емкости
    • Если поток размещения является тем же связанным списком, что и операция потока расширения, поток размещения будет заблокирован.
    • Если связанный список операции запуска потока не был перенесен, то есть головной узел не является ForwardingNode, он может выполняться одновременно
    • Если связанный список операции помещенного потока был перенесен, то есть головным узлом является ForwardingNode, он может помочь в расширении.
  • Ленивая инициализация по сравнению с 1.7
  • емкость представляет предполагаемое количество элементов, а емкость/фабрика используется для расчета начального размера массива, который должен быть близок к
  • loadFactor используется только при расчете начального размера массива, а затем расширение фиксируется на 3/4
  • Проблема расширения при превышении порога дерева, если емкость уже 64, сразу дерево, иначе делать 3 раунда расширения на основе исходной емкости

8. Локальный поток

Требовать

  • Освойте функцию и принцип ThreadLocal
  • Узнайте о времени освобождения памяти ThreadLocal

эффект

  • ThreadLocal может реализовать изоляцию потоков [объектов ресурсов], позволить каждому потоку использовать свои собственные [объекты ресурсов] и избежать проблем с безопасностью потоков, вызванных конфликтами.
  • ThreadLocal также реализует совместное использование ресурсов в потоках.

принцип

Каждый поток имеет переменную-член типа ThreadLocalMap, которая используется для хранения объектов ресурсов.

  • Вызов метода set заключается в использовании самого ThreadLocal в качестве ключа и объекта ресурса в качестве значения и помещении его в коллекцию ThreadLocalMap текущего потока.
  • Вызов метода get заключается в использовании самого ThreadLocal в качестве ключа для поиска связанного значения ресурса в текущем потоке.
  • Вызов метода удаления заключается в использовании самого ThreadLocal в качестве ключа для удаления значения ресурса, связанного с текущим потоком.

Некоторые возможности ThreadLocalMap

  • Хэш-значение ключа равномерно распределено
  • Начальная емкость равна 16, коэффициент расширения равен 2/3, а емкость расширения удваивается.
  • Используйте открытую адресацию для разрешения конфликтов после конфликтов ключевых индексов.

слабый ссылочный ключ

Ключ в ThreadLocalMap разработан как слабая ссылка по следующим причинам.

  • Поток может выполняться в течение длительного времени (например, потоки в пуле потоков), если ключ больше не используется, занимаемая им память должна быть освобождена при нехватке памяти (GC).

время освобождения памяти

  • Пассивный сборщик мусора выпускает ключи
    • Освобождается только память ключа, а память, связанная со значением, освобождаться не будет
  • Ленивое пассивное высвобождение ценности
    • При получении ключа, если он оказался нулевым, освободить его память значений
    • При установке ключа будет использоваться эвристическое сканирование для очистки памяти значений соседнего нулевого ключа.Количество эвристик связано с количеством элементов и найден ли нулевой ключ
  • Активно удалить, чтобы освободить ключ, значение
    • Память ключа и значения будет освобождена одновременно, и память значения соседнего нулевого ключа также будет очищена.
    • Его рекомендуется использовать, потому что он обычно используется как статическая переменная (то есть сильная ссылка) при использовании ThreadLocal, поэтому он не может пассивно полагаться на перезапуск GC.

Supongo que te gusta

Origin blog.csdn.net/weixin_60257072/article/details/132262280
Recomendado
Clasificación