📦 Эта статья и пример исходного кода были заархивированы вjavacore
1. Введение в JUC
Javajava.util.concurrent
Пакет (сокращенно J.U.C) предоставляет большое количество классов инструментов параллелизма, которые являются основным проявлением возможностей параллелизма Java (обратите внимание, что не все, некоторые возможности параллелизма поддерживаются в других пакетах). По функциям его можно условно разделить на:
- Атомарные классы, такие как:
AtomicInteger
,AtomicIntegerArray
,AtomicReference
,AtomicStampedReference
Ждать. - Блокировка - например:
ReentrantLock
,ReentrantReadWriteLock
Ждать. - Параллельные контейнеры, такие как:
ConcurrentHashMap
,CopyOnWriteArrayList
,CopyOnWriteArraySet
Ждать. - Блокирующие очереди - например:
ArrayBlockingQueue
,LinkedBlockingQueue
Ждать. - Неблокирующая очередь - например:
ConcurrentLinkedQueue
,LinkedTransferQueue
Ждать. -
Executor
Framework (пул потоков) - например:ThreadPoolExecutor
,Executors
Ждать.
Мое личное понимание состоит в том, что инфраструктура параллелизма Java может быть разделена на следующие уровни.
Несложно видеть параллельные рамки Java. Класс инструмента в пакете j.u.cc основан наsynchronized
,volatile
,CAS
,ThreadLocal
Создается такой параллельный основной механизм. Итак, чтобы понять характеристики класса инструментов J.U.C, почему у вас есть такая характеристика, вы должны сначала понять эти основные механизмы.
Два, синхронизировано
synchronized
ключевое слово в Java,Используйте механизм блокировки для достижения синхронизации взаимного исключения.
synchronized
Гарантируется, что одновременно только один поток может выполнить метод или блок кода..если не нужен
Lock
,ReadWriteLock
Предоставляемые расширенные функции синхронизации должны иметь приоритетsynchronized
, по следующим причинам:
- После Java 1.6,
synchronized
Было сделано много оптимизаций, и его производительность сравнивалась сLock
,ReadWriteLock
в принципе плоский. С точки зрения тенденций, Java будет продолжать оптимизироваться в будущем.synchronized
, вместоReentrantLock
.ReentrantLock
Это API Oracle JDK, который может не поддерживаться в других версиях JDK; а такжеsynchronized
Это встроенная функция JVM, и поддерживаются все версии JDK.
Использование синхронизированного
synchronized
Есть 3 способа подачи заявки:
- Синхронизированные методы экземпляра- метод обычного синхронизации, замок является текущим экземпляром объекта
-
Синхронизированный статический метод- Для статических синхронизированных методов блокировка относится к текущему классу.
Class
объект -
Блок синхронизированного кода- Для блоков синхронизированных методов блокировка
synchonized
Объекты, указанные в скобках
инструкция:
похожий
Vector
,Hashtable
Этот тип класса синхронизации должен использоватьsynchonized
Украсьте его важные методы, чтобы обеспечить его потокобезопасность.На самом деле такие синхронизированные контейнеры не являются абсолютно потокобезопасными: при обходе итератора и удалении элементов по условиям могут возникать потоконебезопасные условия. Кроме того, Java 1.6 нацелена
synchonized
До оптимизации его производительность была невысокой из-за блокировки.Подводя итог, этот вид контейнера синхронизации постепенно стал ненужным в современных программах Java.
Синхронизированные методы экземпляра
❌ Пример ошибки - несинхронизированный пример
public class NoSynchronizedDemo implements Runnable {
public static final int MAX = 100000;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
NoSynchronizedDemo instance = new NoSynchronizedDemo();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
@Override
public void run() {
for (int i = 0; i < MAX; i++) {
increase();
}
}
public void increase() {
count++;
}
}
// 输出结果: 小于 200000 的随机数字
Синхронизация метода экземпляра Java синхронизируется с объектом, которому принадлежит метод. Таким образом, синхронизация метода каждого экземпляра синхронизируется с другим объектом, то есть с экземпляром, которому принадлежит метод. Только один поток может выполняться в синхронизированном блоке метода экземпляра. Если существует несколько экземпляров, то один поток может одновременно выполнять операции в одном синхронизированном блоке экземпляра. Один экземпляр один поток.
public class SynchronizedDemo implements Runnable {
private static final int MAX = 100000;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo instance = new SynchronizedDemo();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
@Override
public void run() {
for (int i = 0; i < MAX; i++) {
increase();
}
}
/**
* synchronized 修饰普通方法
*/
public synchronized void increase() {
count++;
}
}
Синхронизированный статический метод
Синхронизация статического метода относится к синхронизации объекта класса, в котором находится метод. Поскольку класс может соответствовать только одному объекту класса в JVM, только одному потоку разрешено одновременно выполнять методы статической синхронизации в одном и том же классе.
Для статических синхронизированных методов в разных классах один поток может выполнять статические синхронизированные методы в каждом классе без ожидания. Класс может выполняться только одним потоком за раз, независимо от того, какой статический синхронизированный метод вызывается в классе.
public class SynchronizedDemo2 implements Runnable {
private static final int MAX = 100000;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo2 instance = new SynchronizedDemo2();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
@Override
public void run() {
for (int i = 0; i < MAX; i++) {
increase();
}
}
/**
* synchronized 修饰静态方法
*/
public synchronized static void increase() {
count++;
}
}
Блок синхронизированного кода
Иногда вам не нужно синхронизировать весь метод, но часть метода синхронизации. Java можно синхронизировать со стороны метода.
Обратите внимание, что конструктор блока синхронизации Java имеет круглые скобки для заключения объекта. В приведенном выше примере используйтеthis
, который является экземпляром, вызвавшим метод добавления. Объекты, заключенные в круглые скобки в конструкторе синхронизации, называются объектами монитора. Приведенный выше код использует объект монитора для синхронизации, а метод синхронизированного экземпляра использует экземпляр самого вызывающего метода в качестве объекта монитора.
Только один поток может выполняться внутри метода Java, синхронизированного с одним и тем же объектом монитора.
public class SynchronizedDemo3 implements Runnable {
private static final int MAX = 100000;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo3 instance = new SynchronizedDemo3();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
@Override
public void run() {
for (int i = 0; i < MAX; i++) {
increase();
}
}
/**
* synchronized 修饰代码块
*/
public static void increase() {
synchronized (SynchronizedDemo3.class) {
count++;
}
}
}
Принцип синхронного
synchronized
После компиляции он будет сформирован до и после синхронизированного блока.monitorenter
иmonitorexit
Эти два поступления инструкции, которые требуют, требуют параметра ссылочного типа, чтобы указать объект, который будет заблокирован и разблокирован. еслиsynchronized
Если параметр объекта указан явно, то это ссылка на этот объект, если не указан, то согласноsynchronized
Независимо от того, является ли модификация методом экземпляра или статическим методом, соответствующий экземпляр объекта или объект класса используется в качестве объекта блокировки.
synchronized
Синхронизированные блоки повторно входят в один и тот же поток и не страдают от взаимоблокировок.
synchronized
Синхронизированные блоки являются взаимоисключающими, что означает, что другие потоки, пытающиеся войти, будут заблокированы до тех пор, пока вошедший поток не завершится.
замок механизм
Замок имеет следующие две характеристики:
- мутность: то есть только одному потоку разрешено одновременно удерживать блокировку объекта, и эта функция используется для реализации механизма координации в многопоточности, так что только один поток может получить доступ к блоку кода (составной операции), который требуется синхронизироваться одновременно. Взаимное исключение также часто называют атомарностью операций.
- видимость: Необходимо обеспечить, чтобы допустить замок, модификации, сделанные в общую переменную, видно для другой резьбы, которая впоследствии приобретает блокировку (т. Е. Значение последней общей переменной должно быть получено, когда замок приобретен), в противном случае другой нить Может быть продолжение операции на копии локального кэша, вызывающего несоответствие.
тип замка
-
блокировка объекта- В Java каждый объект будет иметь
monitor
Object, этот объект на самом деле является блокировкой объекта Java, который обычно называют «встроенной блокировкой» или «блокировкой объекта». Может быть несколько объектов класса, поэтому каждый объект имеет свою собственную блокировку объекта, не мешая друг другу. - блокировка класса— В Java также есть блокировка для каждого класса, которую можно назвать «блокировкой класса», и блокировка класса фактически реализуется через блокировку объекта, то есть классовую блокировку объекта класса. Для каждого класса существует только один объект класса, поэтому для каждого класса существует только одна блокировка класса.
СИНХРОНИЗИРОВАННЫЙ
После Java 1.6,
synchronized
Было сделано много оптимизаций, и его производительность сравнивалась сLock
,ReadWriteLock
в принципе плоский.
блокировка спина
Накладные расходы на синхронизацию мьютекса, входящего в состояние блокировки, очень велики, и их следует по возможности избегать. Во многих приложениях общие данные блокируются только на короткий период времени. Идея спин-блокировок состоит в том, чтобы позволить потоку выполнять цикл занятости (вращение) в течение определенного периода времени при запросе блокировки общих данных.Если блокировка может быть получена в течение этого времени, он может избежать входа в состояние блокировки. .
Хотя циклическая блокировка позволяет избежать входа в состояние блокировки и уменьшить накладные расходы, она требует работы цикла занятости, чтобы занимать процессорное время, и подходит только для сценариев, в которых состояние блокировки общих данных очень короткое.
Адаптивные спин-блокировки появились в Java 1.6. Адаптивный означает, что количество вращений больше не фиксировано, а определяется количеством предыдущих вращений на одном и том же замке и состоянием владельца замка.
снятие блокировки
Устранение блокировки относится к устранению блокировок общих данных, которые не могут конкурировать..
Устранение блокировки в основном поддерживается анализом Escape. Если общие данные о куче не могут избежать и получать доступ к другим потокам, то их можно рассматривать как личные данные, а их замки можно устранить.
Для некоторого кода, который не кажется заблокированным, на самом деле неявно добавляется множество блокировок. Например, следующий код объединения строк неявно добавляет блокировку:
public static String concatString(String s1, String s2, String s3) {
return s1 + s2 + s3;
}
String — это неизменяемый класс, и компилятор автоматически оптимизирует конкатенацию String. До Java 1.5 это переводилось в последовательную операцию append() над объектами StringBuffer:
public static String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
В каждом методе Append() есть блок синхронизации. Виртуальная машина наблюдает за переменной SB, вскоре она обнаружит, что ее динамическая область действия ограничена методом created(). То есть все ссылки SB никогда не убегут в метод contATString(), другие потоки не смогут получить к нему доступ, поэтому их можно исключить.
блокировка огрубления
еслиСерия непрерывных операций неоднократно заблокирована и разблокирует один и тот же объект., частые операции блокировки приведут к потере производительности.
Это относится к последовательным методам append() в примере кода в предыдущем разделе. еслиВиртуальная машина обнаруживает, что один и тот же объект заблокирован с помощью такой серии фрагментарных операций, и расширяет (грубо) область блокировки за пределы всей последовательности операций.. В примере кода из предыдущего раздела он расширяется перед первой операцией append() до последней операции append(), которую нужно заблокировать только один раз.
Легкий замок
В Java 1.6 появились предвзятые блокировки и облегченные блокировки, позволяющие блокировкам иметь четыре состояния:
- Незаблокированное состояние (разблокировано)
- Состояние предвзятой блокировки (biasble)
- Легкое заблокированное состояние
- Тяжелое состояние блокировки (надутое)
Легкий замокпо сравнению с традиционным тяжеловесным замком, этоИспользуйте операции CAS, чтобы избежать накладных расходов на тяжелые блокировки с использованием мьютексов.. Для большинства блокировок, не конкурирующих во всем цикле синхронизации и, следовательно, не нужно использовать мьютекс для синхронизации, вы можете синхронизировать с помощью операции CAS, если CAS не работает, то переключите мьютекс для синхронизации.
При попытке получить объект блокировки, если объект блокировки помечен как 0 01, это указывает на то, что блокировка объекта блокировки находится в разблокированном (разблокированном) состоянии. В этот момент виртуальная машина создает запись блокировки в стеке виртуальной машины текущего потока, а затем использует операцию CAS для обновления слова метки объекта до указателя записи блокировки. Если операция CAS выполнена успешно, поток получает блокировку объекта, а флаг блокировки слова метки объекта становится равным 00, указывая на то, что объект находится в состоянии облегченной блокировки.
Блокировка смещения
Идея смещенных замков смещена в сторонуДавайте сначала объект заблокирует поток, поток получает блокировку после того, как больше не нужно синхронизировать, и даже операции CAS больше не нужны..
Три, волатильные
Точка изменчивости
Волатильный - это легкий синхронизированный, который гарантирует «видимость» общих переменных в многопроцессорном развитии.
Видимость означает, что когда один поток изменяет общую переменную, другой поток может прочитать измененное значение.
Как только общая переменная (переменная-член класса, статическая переменная-член класса) изменяется с помощью volatile, она имеет два уровня семантики:
- Это обеспечивает видимость, когда разные потоки работают с этой переменной, то есть поток изменяет значение переменной, и новое значение сразу видно другим потокам.
- Переупорядочивание инструкций отключено.
Если поле объявлено как volatile, модель памяти потоков Java гарантирует, что все потоки будут видеть одно и то же значение переменной.
Использование изменчивого
еслиvolatile
Модификаторы переменных при правильном использовании меньшеsynchronized
Использование и реализация более низкой стоимости, потому что это не вызывает переключение контекста потока и планирование. но,volatile
незаменимыйsynchronized
,так какvolatile
Атомарность операции не гарантируется.
как правило,использоватьvolatile
Вы должны соответствовать следующим двум условиям:
- Запись в переменную не зависит от текущего значения
- Переменная не содержится в инварианте с другими переменными
Пример: количество тегов состояния
volatile boolean flag = false;
while(!flag) {
doSomething();
}
public void setFlag() {
flag = true;
}
Пример: двойная блокировка, реализующая потокобезопасный одноэлементный класс
class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
Принцип энергозависимости
Наблюдая за ассемблерным кодом, сгенерированным, когда ключевое слово volatile добавлено, а ключевое слово volatile не добавлено, обнаружено, чтоПрисоединяйсяvolatile
ключевое слово, будет еще одноlock
инструкция префикса.
lock
Префиксная инструкция фактически эквивалентна барьеру памяти.(также называемый барьером памяти), барьер памяти будет выполнять 3 функции:
- Это гарантирует, что следующие инструкции не будут поставлены в очередь перед барьером памяти во время переупорядочивания инструкций, а предыдущие инструкции не будут поставлены в очередь позади барьера памяти; то есть, когда инструкция к барьеру памяти выполняется, предыдущая инструкция будет Операция завершена;
- Это заставляет изменения в кеше немедленно записываться в основную память;
- В случае операции записи она делает недействительной соответствующую строку кэша в других процессорах.
4. КАС
Основные моменты CAS
Взаимоисключающая синхронизация является наиболее распространенной гарантией правильности параллелизма.
Основная проблема синхронизации взаимного исключения — это проблема производительности, вызванная блокировкой и пробуждением потока., поэтому синхронизация взаимного исключения также называется блокирующей синхронизацией. Синхронизация с взаимным исключением — это пессимистичная стратегия параллелизма, при которой всегда думают, что пока не будут приняты правильные меры по синхронизации, проблемы обязательно будут. Независимо от того, есть ли конкуренция за общие данные, они должны быть заблокированы (это концептуальная модель, обсуждаемая здесь, на самом деле, виртуальная машина оптимизирует большую часть ненужных блокировок), преобразование режима ядра пользовательского режима, счетчик блокировки обслуживания и проверка если есть заблокированные темы, которые нужно разбудить и т.д.
По мере развития набора аппаратных инструкций мы можем использовать оптимистическую стратегию параллелизма, основанную на обнаружении конфликтов: сначала выполнить операцию, если никакие другие потоки не соревнуются за общие данные, тогда операция завершается успешно, в противном случае предпринимать компенсирующие меры (повторять попытки до тех пор, пока они не завершатся успешно) до тех пор, пока) . Многие реализации этой оптимистичной стратегии параллелизма не требуют блокировки потоков, поэтому такая синхронизация называется неблокирующей синхронизацией.
Зачем нужна оптимистическая блокировка?Разработка набора аппаратных инструкцийпродолжать?因为需要操作和冲突检测这两个步骤具备原子性。而这点是由硬件来完成,如果再使用互斥同步来保证就失去意义了。硬件支持的原子性操作最典型的是:CAS。
CAS (Сравнить и поменять местами)Закон, буквальноСравните и обменяйте. CAS имеет 3 операнда, а именно: значение памяти V, старое ожидаемое значение A и новое значение B, которое необходимо изменить. Измените значение памяти V на B тогда и только тогда, когда ожидаемое значение A и значение памяти V совпадают, в противном случае ничего не делайте.
Принципы КАС
Как Java реализует CAS?
Java в основном используетUnsafe
Операции CAS, предоставляемые этим классом.
Unsafe
CAS зависит от реализации JVM для разных операционных систем.Atomic::cmpxchg
инструкция.
Atomic::cmpxchg
Реализация использует операции CAS в сборке и использует предоставляемые ЦПlock
Сигналы гарантируют свою атомарность.
Применение КАС
Атомный класс
Атомарные классы — наиболее типичное применение CAS в Java.
Давайте сначала посмотрим на общий фрагмент кода.
if(a==b) {
a++;
}
еслиa++
Что, если значение a будет изменено перед выполнением? Могу ли я получить ожидаемое значение? Причина этой проблемы в том, что в параллельной среде приведенные выше фрагменты кода не являются атомарными операциями и могут быть изменены другими потоками в любое время.
Самый классический способ решить эту проблему — применить атомарные классы.incrementAndGet
метод.
public class AtomicIntegerDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
final AtomicInteger count = new AtomicInteger(0);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
count.incrementAndGet();
}
});
}
executorService.shutdown();
executorService.awaitTermination(3, TimeUnit.SECONDS);
System.out.println("Final Count is : " + count.get());
}
}
Поставляется в пакете J.U.C.AtomicBoolean
,AtomicInteger
,AtomicLong
соответственно дляBoolean
,Integer
,Long
Для выполнения атомарных операций операции в целом аналогичны вышеприведенным примерам и подробно описываться не будут.
блокировка спина
Используя атомарные классы (по сути, CAS), можно реализовать спин-блокировки.
Так называемые спиновые замки, относятся к потоке несколько раз, проверяют переменную блокировки, пока она не удается. Поскольку нить остается в реализации этого процесса, это занятое ожидание. После приобретения спинового блокировки нить останутся заблокированными до тех пор, пока явно не выделяет спиновую замок.
Пример: пример без поддержки потоков
public class AtomicReferenceDemo {
private static int ticket = 10;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
}
static class MyThread implements Runnable {
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");
ticket--;
}
}
}
}
Выходной результат:
pool-1-thread-2 卖出了第 10 张票
pool-1-thread-1 卖出了第 10 张票
pool-1-thread-3 卖出了第 10 张票
pool-1-thread-1 卖出了第 8 张票
pool-1-thread-2 卖出了第 9 张票
pool-1-thread-1 卖出了第 6 张票
pool-1-thread-3 卖出了第 7 张票
pool-1-thread-1 卖出了第 4 张票
pool-1-thread-2 卖出了第 5 张票
pool-1-thread-1 卖出了第 2 张票
pool-1-thread-3 卖出了第 3 张票
pool-1-thread-2 卖出了第 1 张票
Очевидно, что имело место дублирование продажи билетов.
Пример: использование спин-блокировок для безопасности потоков
Безопасность потока может быть гарантирована с помощью неблокирующей синхронизации, такой как спин-блокировки, которые используются ниже.AtomicReference
реализовать спин-блокировку.
public class AtomicReferenceDemo2 {
private static int ticket = 10;
public static void main(String[] args) {
threadSafeDemo();
}
private static void threadSafeDemo() {
SpinLock lock = new SpinLock();
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread(lock));
}
executorService.shutdown();
}
static class SpinLock {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while (!atomicReference.compareAndSet(null, current)) {}
}
public void unlock() {
Thread current = Thread.currentThread();
atomicReference.compareAndSet(current, null);
}
}
static class MyThread implements Runnable {
private SpinLock lock;
public MyThread(SpinLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (ticket > 0) {
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");
ticket--;
}
lock.unlock();
}
}
}
}
Выходной результат:
pool-1-thread-2 卖出了第 10 张票
pool-1-thread-1 卖出了第 9 张票
pool-1-thread-3 卖出了第 8 张票
pool-1-thread-2 卖出了第 7 张票
pool-1-thread-3 卖出了第 6 张票
pool-1-thread-1 卖出了第 5 张票
pool-1-thread-2 卖出了第 4 张票
pool-1-thread-1 卖出了第 3 张票
pool-1-thread-3 卖出了第 2 张票
pool-1-thread-1 卖出了第 1 张票
Проблемы с CAS
В нормальных условиях CAS более заблокирован. Поскольку CAS является неблокирующим алгоритмом, он позволяет избежать времени ожидания блокировки и пробуждения потока.
Однако есть некоторые проблемы с CAS.
АВА-проблема
Если переменная была сначала прочитана со значением A, ее значение было изменено на B, а затем изменено обратно на A, операция CAS ошибочно приняла бы это за тот факт, что оно никогда не изменялось.
J.U.C Пакет предоставляет атомный эталонный класс с тегомAtomicStampedReference
Чтобы решить эту проблему, он может гарантировать правильность CAS, контролируя версию значения переменной. В большинстве случаев проблема ABA не повлияет на корректность параллелизма программ.Если вам нужно решить проблему ABA, может быть эффективнее использовать традиционную синхронизацию взаимного исключения, чем атомарный класс.
Длительное время цикла и высокие накладные расходы
Spin CAS (продолжайте попытки до тех пор, пока не добьетесь успеха) приводит к очень большим накладным расходам на выполнение ЦП, если он не работает в течение длительного времени.
Если JVM может поддерживатьpause
Эффективность инструкции будет улучшена в определенной степени.pause
Директивы служат двумя целями:
- Он может задерживать выполнение конвейера (de-pipeline), чтобы ЦП не потреблял слишком много ресурсов выполнения, время задержки зависит от конкретной версии реализации, а на некоторых процессорах время задержки равно нулю.
- Это может избежать очистки конвейера ЦП, вызванной нарушением порядка памяти при выходе из цикла, тем самым повышая эффективность выполнения ЦП.
Он потребляет больше ресурсов ЦП и будет выполнять бесполезную работу, даже если она бесполезна.
Только одна общая переменная гарантированно будет атомарной.
При выполнении операций над общей переменной мы можем использовать циклический CAS для обеспечения атомарных операций, но при работе с несколькими общими переменными циклический CAS не может гарантировать атомарность операции, и в это время можно использовать блокировки.
Или есть хитрый способ объединить несколько общих переменных в одну общую переменную для работы. Например, есть две общие переменныеi = 2, j = a
Слияниеij=2a
, а затем используйте CAS для работыij
. Начиная с Java 1.5 JDK предоставляетAtomicReference
Класс для обеспечения атомарности между ссылочными объектами, вы можете поместить несколько переменных в один объект для выполнения операций CAS.
5. Локальный поток
ThreadLocal
это служебный класс для хранения локальных копий потока.Для обеспечения безопасности потоков синхронизация не нужна. Синхронизация предназначена только для обеспечения правильности совместных контенций общих данных. Если метод не включает в себя общие данные, это естественно ненужно синхронизировать.
на ЯвеНет схемы синхронизацииимеют:
- реентерабельный код- Также называется чистым кодом. Если метод, тоРезультаты возврата предсказуемыОднако до тех пор, пока можно ввести одни и те же данные, он может вернуть тот же результат, который должен удовлетворять повторное использование, конечно, поток безопасен.
- Локальное хранилище потоков- использовать
ThreadLocal
Локальная копия создается в каждом потоке для общих переменных., эта копия может быть доступна только текущему потоку и не может быть доступна другим потокам, поэтому она естественно потокобезопасна.
Использование ThreadLocal
ThreadLocal
Методы:
public class ThreadLocal<T> {
public T get() {}
public void set(T value) {}
public void remove() {}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {}
}
инструкция:
get
- для полученияThreadLocal
Копия переменной, сохраненная в текущем потоке.set
- Используется для установки копии переменной в текущем потоке.remove
initialValue
Повторная инициализация метода, если его значение не установлено текущим потоком в промежуточном потоке. Это может привести к множественным вызовам в текущем потоке.initialValue
метод.initialValue
- установить значение по умолчанию для ThreadLocalget
Исходное значение, необходимо переписатьinitialValue
метод .
ThreadLocal
Часто используется для предотвращения совместного использования изменяемых переменных Singleton или глобальных переменных. Типичные сценарии применения: управление подключениями и сеансами базы данных.
Пример - соединение с базой данных
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
@Override
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
Пример — управление сеансом
private static final ThreadLocal<Session> sessionHolder = new ThreadLocal<>();
public static Session getSession() {
Session session = (Session) sessionHolder.get();
try {
if (session == null) {
session = createSession();
sessionHolder.set(session);
}
} catch (Exception e) {
e.printStackTrace();
}
return session;
}
Пример - полный пример использования
public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
}
static class MyThread implements Runnable {
@Override
public void run() {
int count = threadLocal.get();
for (int i = 0; i < 10; i++) {
try {
count++;
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
threadLocal.set(count);
threadLocal.remove();
System.out.println(Thread.currentThread().getName() + " : " + count);
}
}
}
Количество всех выходов = 10
Принцип ThreadLocal
структура хранения
Thread
класс поддерживаетThreadLocal.ThreadLocalMap
члены типаthreadLocals
. Этот член используется для хранения копии исключительной для потока переменной.
ThreadLocalMap
даThreadLocal
внутренний класс, который поддерживаетEntry
Множество,Entry
Для хранения пар клавиши, которые являются ключомThreadLocal
Objects, Value — переданный объект (копия переменной).
Как разрешить хеш-конфликты
ThreadLocalMap
Хотя похожиMap
структурировать структуру данных, но не реализуетMap
интерфейс. это не поддерживаетMap
в интерфейсеnext
метод, а значитThreadLocalMap
Способ разрешения хеш-конфликтов встол на молнииСпособ.
По факту,ThreadLocalMap
Используйте линейное обнаружение для разрешения конфликтов хэшей. Так называемое линейное обнаружение заключается в определении позиции элемента в массиве таблиц по значению хеш-кода исходного ключа, и если обнаруживается, что эта позиция была занята другими значениями ключа, используется фиксированный алгоритм поиска. следующую позицию с определенным размером шага, и судите по очереди, пока не найдете место для ее сохранения.
проблема с утечкой памяти
ThreadLocalmapEntry
наследоватьWeakReference
, поэтому его ключ (ThreadLocal
object) — слабая ссылка, а value (копия переменной) — сильная ссылка.
- если
ThreadLocal
объект не имеет внешней сильной ссылки для ссылки на него, тоThreadLocal
Объект будет собран на следующей сборке мусора. - В настоящее время,
Entry
KEY в середине был переработан, но Value не будет переработан сборщиком мусора, потому что это сильная ссылка. Если вы создадитеThreadLocal
Поток продолжает работать, тогда значение не будет постоянно перерабатываться, что приведет к утечке памяти.
Итак, как избежать утечек памяти? Метод:использоватьThreadLocal
изset
После метода отображаемый вызовremove
метод.
ThreadLocal<String> threadLocal = new ThreadLocal();
try {
threadLocal.set("xxx");
// ...
} finally {
threadLocal.remove();
}
использованная литература
- «Практика параллельного программирования на Java»
- Искусство параллельного программирования на Java
- "Глубокое понимание виртуальной машины Java"
- Параллельное программирование на Java: анализ ключевых слов volatile
- Параллельное программирование на Java: синхронизировано
- Глубокое понимание принципа синхронизированной реализации параллелизма Java.
- Полная экспликация Java CAS
- Подробное объяснение CAS в Java
- ThreadLocal Ultimate
- Принцип синхронной реализации и оптимизация блокировок
- Non-blocking Algorithms