предисловие
Персональная коллекция из 80 классических вопросов для интервью по многопоточности/параллельности Java, теперь дайте 11-20 ответов и загрузите их на github~
11. Зачем использовать пул потоков? Внутренний механизм пула потоков Java, роли параметров, несколько очередей блокировки работы, типы пулов потоков и сценарии использования
Ответьте на эти пункты:
- Зачем использовать пул потоков?
- Принцип пула потоков Java
- Основные параметры пула потоков
- Несколько очередей блокировки работы
- Неправильное использование пулов потоков
- Типы пулов потоков и сценарии использования
Зачем использовать пул потоков?
Пул потоков: пул, который управляет потоками.
- Управляйте потоками, чтобы избежать увеличения потребления ресурсов при создании и уничтожении потоков.
- Улучшить отзывчивость.
- повторное использование.
Принцип выполнения пула потоков Java
Чтобы наглядно описать выполнение пула потоков, используйте аналогию:
- Основная нить уподобляется штатным сотрудникам компании.
- Непрофильные потоки уподобляются аутсорсинговым сотрудникам
- Очереди блокировки сравниваются с пулами запросов
- Отправка задачи похожа на запрос
Основные параметры пула потоков
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize: максимальное количество основных потоков в пуле потоков.
- maxPoolSize: максимальное количество потоков в пуле потоков.
- keepAliveTime: размер времени простоя неосновных потоков в пуле потоков.
- unit: единица времени выживания потока бездействия
- workQueue: блокирующая очередь для хранения задач
- threadFactory: используется для установки фабрики для создания потоков.Вы можете установить осмысленные имена для созданных потоков, чтобы облегчить устранение неполадок.
- обработчик: событие стратегии насыщения линии города, в основном существует четыре типа стратегий отказа.
Четыре стратегии отказа
- AbortPolicy (выдает исключение, по умолчанию)
- DiscardPolicy (отбрасывать задачи напрямую)
- DiscardOldestPolicy (отбросить самую старую задачу в очереди и продолжить отправку текущей задачи в пул потоков)
- CallerRunsPolicy (передается потоку, в котором находится вызов пула потоков для обработки)
Несколько очередей блокировки работы
- ArrayBlockingQueue (ограниченная очередь блокировки, реализованная с помощью массива, отсортированного по принципу FIFO)
- LinkedBlockingQueue (очередь блокировки на основе структуры связанного списка, сортировка задач в соответствии с FIFO, емкость может быть установлена опционально, если не задана, это будет неограниченная очередь блокировки)
- DelayQueue (очередь для отложенного выполнения задачи на определенный период времени)
- PriorityBlockingQueue (неограниченная очередь блокировки с приоритетом)
- SynchronousQueue (блокирующая очередь, в которой не хранятся элементы, каждая операция вставки должна ждать, пока другой поток не вызовет операцию удаления, иначе операция вставки всегда блокируется)
Неправильное использование пулов потоков
Неправильное применение пулов потоков может привести к проблемам с нехваткой памяти.
Если вам интересно, вы можете прочитать мою статью:Анализ исходного кода — проблема увеличения памяти, вызванная пулом потоков newFixedThreadPool
Типы пулов потоков и сценарии использования
- newFixedThreadPool
Он подходит для обработки ресурсоемких задач, гарантируя, что при длительном использовании ЦП рабочими потоками выделяется как можно меньше потоков, то есть он подходит для выполнения долгосрочных задач.
- newCachedThreadPool
Используется для одновременного выполнения большого количества краткосрочных небольших задач.
- newSingleThreadExecutor
Он подходит для сценариев, в которых задачи выполняются последовательно, по одной задаче за раз.
- newScheduledThreadPool
Сценарии, в которых задачи выполняются периодически, и сценарии, в которых необходимо ограничить количество потоков.
- newWorkStealingPool
Создайте пул потоков с достаточным количеством потоков для поддержания соответствующего уровня параллелизма.Он будет использовать перехват работы, чтобы многоядерный ЦП не простаивал, и всегда были живые потоки для запуска ЦП, что по сути является ForkJoinPool . )
Если вам интересно, вы можете прочитать мою статью:Требования к собеседованию: анализ пула потоков Java
12. Поговорите о понимании ключевого слова volatile
Волатильность — это вопрос, который очень любят задавать интервьюеры, и он может ответить на следующие пункты:
- Роль переменной vlatile
- Модели памяти современных компьютеров (методы сниффинга, протокол МЭСИ, шины)
- Модель памяти Java (JMM)
- Что такое видимость?
- изменение порядка инструкций
- семантика энергозависимой памяти
- as-if-serial
- Happens-before
- Может ли volatile решить атомарность? Почему?
- Основной принцип volatile, как обеспечить видимость и запретить перестановку инструкций (барьер памяти)
Что делает переменная vlatile?
- Гарантированная переменная видимость для всех потоков
- Отключить перестановку инструкций
Модель памяти современных компьютеров
- Кэш включает кэш L1, L2, L3~
- Протокол когерентности кеша, вы можете понять протокол MESI
- Шина (Bus) — общая коммуникационная магистраль для передачи информации между различными функциональными компонентами компьютера.
- Процессор использует методы прослушивания, чтобы убедиться, что его внутренний кэш, системная память и кэшированные данные других процессоров непротиворечивы на шине.
Модель памяти Java (JMM)
Что такое видимость?
Видимость означает, что когда один поток изменяет общую переменную, другой поток может прочитать измененное значение.
изменение порядка инструкций
Перестановка инструкций означает, что в процессе выполнения программы для повышения производительности компилятор и ЦП могут переупорядочивать инструкции.
семантика энергозависимой памяти
- При записи изменчивой переменной JMM сбрасывает значение общей переменной из соответствующей локальной памяти потока в основную память.
- При чтении энергозависимой переменной JMM аннулирует соответствующую локальную память потока. Затем поток будет читать общую переменную из основной памяти.
as-if-serial
При наблюдении внутри этого потока все операции упорядочены, то есть как бы ни переупорядочены (компилятор и процессор для увеличения параллелизма), результат выполнения (однопоточной) программы не изменится.
double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r; //C
Шаг C зависит от шагов A и B. Из-за перестановки инструкций последовательность выполнения программы может быть A->B->C или B->A->C, но C не может быть выполнена раньше A или B. , что нарушило бы семантику "как-будто-сериал".
Happens-before
В языке Java существует принцип «происходит до»:
- правила порядка программы: В потоке, в порядке потока управления, операция, написанная впереди, выполняется до операции, написанной сзади.
- Правила блокировки монитора: сначала выполняется операция разблокировки, а затем выполняется такая же операция блокировки.
- правила изменяемой переменной: запись в переменную происходит перед последующим чтением переменной
- правило начала потока: метод start() объекта Thread выполняется первым для каждого действия этого потока.
- правила завершения потока: Все операции в потоке выполняются первыми при обнаружении завершения потока.Мы можем определить, что поток завершил выполнение, через конец метода Thread.join() и возвращаемое значение Thread.isAlive().
- Правила прерывания потока: вызов метода прерывания потока () происходит первым, когда код прерванного потока обнаруживает возникновение события прерывания.
- Правила завершения объекта: инициализация объекта происходит сначала в начале его метода finalize().
- переходность: Если операция А выполняется до операции В, а операция В выполняется до операции С, можно сделать вывод, что операция А выполняется до операции С.
Может ли volatile решить атомарность? Почему?
Нет, вы можете прямо привести пример i++, атомарность требует гарантии синхронизации или блокировки
public class Test {
public volatile int race = 0;
public void increase() {
race++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<100;j++)
test.increase();
};
}.start();
}
//等待所有累加线程结束
while(Thread.activeCount()>1)
Thread.yield();
System.out.println(test.race);
}
}
Основной принцип volatile, как обеспечить видимость и запретить перестановку инструкций (барьер памяти)
Когда изменяемые изменяемые переменные преобразуются в ассемблерный код, будет найдена дополнительная инструкция префикса блокировки. Инструкция блокировки эквивалентна барьеру памяти, который гарантирует следующее:
- 1. При переупорядочивании следующие инструкции нельзя переупорядочить до положения перед барьером памяти.
- 2. Записать кэш этого процессора в память
- 3. Если это действие записи, оно сделает недействительным соответствующий кеш в других процессорах.
2, 3 точки для обеспечения видимости, точка 1 запрещает изменение порядка ~
Заинтересованные друзья могут прочитать мою статью:Собеседование с Java-программистом обязательно: комплексный анализ изменчивости
13. Компоненты АСК, принцип реализации
AQS, а именно AbstractQueuedSynchronizer, представляет собой базовую структуру для создания блокировок или других компонентов синхронизации.Он использует переменную-член int для представления состояния синхронизации и завершает постановку в очередь потоков получения ресурсов через встроенную очередь FIFO. Можно ответить на следующие ключевые моменты:
- поддержание гос.
- очередь CLH
- уведомление ConditionObject
- Шаблон проектирования метода шаблона
- Эксклюзивные и общие режимы.
- Пользовательский синхронизатор.
- Некоторые расширения корзины семейства AQS, такие как: ReentrantLock и т. д.
поддержание состояния
- Состояние, переменная int, состояние блокировки, украшенное volatile для обеспечения видимости в многопоточности.
- Методы getState() и setState() доработаны, чтобы запретить подклассам AQS переопределять их.
- В методе compareAndSetState() используется алгоритм CAS с идеей оптимистичной блокировки для обеспечения безопасности потоков и состояния.
Атомарность настроек.
Друзья, кому интересен CAS, можете прочитать мою статью~Практика оптимистической блокировки CAS для решения проблем параллелизма
очередь CLH
CLH (замки Крейга, Лэндина и Хагерстена) очередь синхронизацииЭто двусторонняя очередь FIFO, которая записывает элементы головы и хвоста очереди через узлы головы и хвоста, а тип элементов очереди — Node. AQS полагается на него для завершения управления состоянием синхронизации.Если текущему потоку не удается получить состояние синхронизации, AQS создаст состояние ожидания текущего потока и другую информацию в узле (узле) и добавит его в очередь синхронизации CLH. , пока блокируется. Текущий поток, когда состояние синхронизации освобождается, разбудит первый узел (справедливая блокировка), чтобы он снова попытался получить состояние синхронизации.
уведомление ConditionObject
Мы все знаем, что когда синхронизация управляет синхронизацией, мы можем взаимодействовать с методами Object wait(), notify() и notifyAll() для реализации режима ожидания/уведомления. А как же Лок? Он предоставляет интерфейс Condition, а также может реализовать механизм ожидания/уведомления с помощью таких методов, как await(), signal() и signalAll(). ConditionObject реализует интерфейс Condition и обеспечивает поддержку переменных условия для AQS.
Отношения любви и ненависти между очередью ConditionObject и очередью CLH:
- Поток, вызывающий метод await(), будет добавлен в очередь ожидания conditionObject и разбудит следующий узел головного узла в очереди CLH.
- После того как поток вызовет метод singnal() для объекта ConditionObject, firstWaiter в очереди ожидания будет добавлен в очередь CLH AQS, ожидая пробуждения.
- Когда поток вызывает метод unLock() для снятия блокировки, следующий узел головного узла в очереди CLH (в данном случае firtWaiter) будет разбужен.
Шаблон проектирования метода шаблона
Что такое Шаблон проектирования шаблонов?
Определите скелет алгоритма в методе и отложите некоторые шаги до подклассов. Шаблонные методы позволяют подклассам переопределять определенные шаги в алгоритме без изменения структуры алгоритма.
Типичным шаблоном проектирования AQS является шаблон проектирования метода шаблона. Производная реализация корзины семейства AQS (ReentrantLock, Semaphore) отражает этот шаблон проектирования. Например, AQS предоставляет шаблонные методы, такие как tryAcquire и tryAcquireShared, для реализации пользовательских синхронизаторов для подклассов.
Эксклюзивный и общий режим
- Эксклюзивный: только один поток одновременно поддерживает состояние синхронизации, например ReentrantLock. Его можно разделить на честный замок и несправедливый замок.
- Общий режим: несколько потоков могут выполняться одновременно, например Semaphore/CountDownLatch и т. д., все они являются общими продуктами.
Пользовательский синхронизатор
Если вы хотите реализовать пользовательскую блокировку, вам сначала нужно определить, хотите ли вы реализовать эксклюзивную блокировку или общую блокировку, определить значение состояния атомарной переменной, определить внутренний класс для наследования AQS и переписать соответствующий метод шаблона. .
Некоторые расширения семейства AQS Bucket.
Семафор, CountDownLatch, ReentrantLock
Вы можете прочитать мою предыдущую статью,Анализ AQS и реальный бой
14. Что такое псевдосовместное использование в многопоточной среде
- Что такое ложный обмен
- Как решить проблему ложного обмена
Что такое ложный обмен
Определение псевдо-акции?
Кэш ЦП кэшируется в единицах строк кэша.Когда несколько потоков изменяют переменные, которые не зависят друг от друга, и эти переменные находятся в одной строке кэша, они будут влиять на производительность друг друга. Это ложный обмен
Современная компьютерная модель расчета, у всех впечатление, верно? Я уже говорил об этой статье ранее, если вам интересно, вы можете прочитать ее.Собеседование с Java-программистом обязательно: комплексный анализ изменчивости
- Скорость выполнения ЦП на несколько порядков выше, чем у памяти.Чтобы повысить эффективность выполнения, современные компьютерные модели развили модели ЦП, кэш-памяти (L1, L2, L3) и памяти.
- Когда ЦП выполняет операцию, если данные сначала запрашиваются из кеша L1, если они не могут быть найдены, то он переходит в кеш L2, чтобы найти их, и так далее, пока данные не будут получены в памяти.
- Чтобы избежать частой выборки данных из памяти, умные ученые разработали строку кэша размером 64 байта.
Также из-за строки кэша существует проблема ложного совместного использования, как показано на рисунке:
Предположим, что данные a, b загружаются в одну и ту же строку кэша.
- Когда поток 1 изменяет значение a, CPU1 уведомляет другие ядра ЦП о том, что текущая строка кэша признана недействительной.
- В это время, если поток 2 инициирует изменение b, потому что строка кэша была признана недействительной, поэтомуЗатем core2 перечитает данные строки кэша из основной памяти.. После чтения, поскольку он хочет изменить значение b, ЦП 2 уведомляет другие ядра ЦП о том, что текущая строка кэша (строка кэша) снова недействительна.
- Соус фиолетовый, если содержимое одной и той же строки Cache читается и записывается несколькими потоками, легко конкурировать друг с другом, а частая обратная запись в основную память сильно снизит производительность.
Как решить проблему ложного обмена
Поскольку ложное совместное использование вызвано тем, что независимые переменные хранятся в одной и той же строке кэша, размер строки кэша составляет 64 байта. Тогда мы можемИспользуйте пространство вместо времени, то есть способ заполнения данных, рассредоточение независимых переменных по разным строкам кэша~
Демонстрационный пример с общей памятью:
public class FalseShareTest {
public static void main(String[] args) throws InterruptedException {
Rectangle rectangle = new Rectangle();
long beginTime = System.currentTimeMillis();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
rectangle.a = rectangle.a + 1;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
rectangle.b = rectangle.b + 1;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("执行时间" + (System.currentTimeMillis() - beginTime));
}
}
class Rectangle {
volatile long a;
volatile long b;
}
результат операции:
执行时间2815
Длина длинного типа составляет 8 байт, мы не добавляем 7 переменных длинного типа между переменными a и b, каков результат вывода? следующим образом:
class Rectangle {
volatile long a;
long a1,a2,a3,a4,a5,a6,a7;
volatile long b;
}
результат операции:
执行时间1113
Можно обнаружить, что метод заполнения данных можно использовать для разделения переменных чтения и записи на разные строки кэша, что может быть очень высокопроизводительным~
15. В чем разница между Runnable и Callable?
- Метод интерфейса Callable — call(), а метод Runnable — run();
- Метод call интерфейса Callable имеет возвращаемое значение и поддерживает дженерики, а метод run интерфейса Runnable не имеет возвращаемого значения.
- Метод вызываемого интерфейса call() позволяет генерировать исключения, в то время как метод run() интерфейса Runnable не может продолжать генерировать исключения;
@FunctionalInterface
public interface Callable<V> {
/**
* 支持泛型V,有返回值,允许抛出异常
*/
V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
/**
* 没有返回值,不能继续上抛异常
*/
public abstract void run();
}
Взгляните на демо-код, его должно быть легче понять~
/*
* @Author 捡田螺的小男孩
* @date 2020-08-18
*/
public class CallableRunnableTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Callable <String> callable =new Callable<String>() {
@Override
public String call() throws Exception {
return "你好,callable";
}
};
//支持泛型
Future<String> futureCallable = executorService.submit(callable);
try {
System.out.println(futureCallable.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("你好呀,runnable");
}
};
Future<?> futureRunnable = executorService.submit(runnable);
try {
System.out.println(futureRunnable.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
executorService.shutdown();
}
}
результат операции:
你好,callable
你好呀,runnable
null
16. Разница между ожиданием(), уведомлением() и приостановкой(), возобновление()
- wait() заставляет поток войти в блокирующее состояние ожидания и освобождает блокировку
- notify() пробуждает поток в состоянии ожидания, обычно используется в сочетании с методом wait().
- suspend() переводит поток в состояние блокировки и не возобновляет его автоматически. Соответствующее возобновление() необходимо вызвать, чтобы поток снова перешел в состояние исполняемого файла. Метод suspend() может легко вызвать взаимоблокировку.
- Метод возобновления() используется в сочетании с методом suspend().
приостановка () устарелаПосле вызова метода suspend() поток не будет освобождать уже занятые ресурсы (например, блокировки), а войдет в состояние сна после занятия ресурсов, что легко может вызвать проблемы взаимоблокировки.
17. Интерфейс условия и принцип его реализации
- Сравнение интерфейса состояния и метода монитора объектов
- Условный интерфейс с использованием демо
- Принцип реализации условия
Сравнение интерфейса состояния и метода монитора объектов
Объект Java (Object) предоставляет методы серии wait(), notify(), notifyAll(), с синхронизацией вы можете реализовать режим ожидания/уведомления. Интерфейс Condition взаимодействует с Lock для реализации аналогичного механизма ожидания/уведомления через await(), signal(), signalAll() и другие методы.
Контраст | метод наблюдения за объектом | Condition |
---|---|---|
Предварительные условия | получить блокировку объекта | Вызовите Lock.lock(), чтобы получить блокировку, и вызовите Lock.newCondition(), чтобы получить объект Condition. |
метод вызова | Звоните напрямую, object.wait() | Звоните напрямую, condition.await() |
количество ожидающих очередей | 1 | несколько |
Текущий поток снимает блокировку и переходит в состояние ожидания. | служба поддержки | служба поддержки |
Не отвечает на прерывания в состоянии ожидания | не поддерживается | служба поддержки |
Текущий поток снимает блокировку и переходит в состояние ожидания тайм-аута. | служба поддержки | служба поддержки |
Текущий поток освобождает блокировку и переходит в состояние ожидания до определенного момента в будущем. | не поддерживается | служба поддержки |
Разбудить поток в очереди ожидания | служба поддержки | служба поддержки |
Разбудить все потоки в очереди ожидания | служба поддержки | служба поддержки |
Условный интерфейс с использованием демо
public class ConditionTest {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
}
Принцип реализации условия
Фактически, типы узлов в очереди синхронизации и очереди ожидания являются статическим внутренним классом AbstractQueuedSynchronizer.Node синхронизатора.Далее проиллюстрируем принцип реализации Condition~
Базовая структура очереди ожидания
Условие содержит очередь ожидания, а условие имеет первый узел (firstWaiter) и хвостовой узел (lastWaiter). Текущий поток вызывает метод Condition.await(), который создаст узел с текущим потоком и добавит узел в очередь ожидания с конца.
Структурная схема AQS
ConditionI используется вместе с Lock, а нижний уровень связан с синхронизатором (AQS). Синхронизатор имеет очередь синхронизации и несколько ожидающих очередей~
ждать
При вызове метода await() первый узел очереди синхронизации (узел, получивший блокировку) перемещается в очередь ожидания условия.
уведомлять
Вызов метода signal() команды Condition разбудит узел (первый узел), который дольше всего ожидает в очереди ожидания.
Прежде чем разбудить узел, узел перемещается в очередь синхронизации.
18. Как настроить пул потоков и как подтвердить максимальное количество?
В книге "Java Concurrency in Practice" есть формула для оценки размера потока пула потоков
Nthreads=NcpuUcpu(1+w/c)
- Ncpu = общее количество ядер процессора
- Ucpu = загрузка процессора, 0~1
- W/C = отношение времени ожидания ко времени вычислений
Предполагая, что процессор работает на 100%, формула
Nthreads=Ncpu*(1+w/c)
Если прикинуть, Цзян Цзы:
- еслиПриложения с интенсивным вводом-выводом(например, взаимодействие с данными базы данных, загрузка и выгрузка файлов, передача данных по сети и т. д.), операции ввода-вывода обычно занимают много времени, а отношение времени ожидания к времени вычислений (w/c) будет больше 1, поэтому оптимальное количество потоков оценивается как Nthreads=Ncpu *(1+1) = 2Ncpus.
- еслиПриложения, интенсивно использующие процессор(например, программа со сложным алгоритмом), идеальная ситуация, без ожидания, w=0, Nthreads=Ncpu. Для задач с интенсивными вычислениями в системе с N процессорами, когда размер пула потоков составляет N+1, обычно достигается оптимальная эффективность. Итак, Nthreads = Ncpu+1
Есть конкретная ссылка? Например
Например, среднее время работы ЦП каждого потока составляет 0,5 с, время ожидания потока (время работы без ЦП, например, ввод-вывод) составляет 1,5 с, а количество ядер ЦП равно 8, тогда в соответствии с приведенной выше формулой его можно оценить: Размер пула потоков = (1+ 1,5/05)*8=32.
Со ссылкой на эту статью в интернете она очень хорошо написана.Заинтересованные друзья могут ее глянуть:
19. Предположим, что есть три потока T1, T2 и T3. Как обеспечить выполнение T2 после выполнения T1 и выполнение T3 после выполнения T2?
можно использоватьметод присоединениярешить эту проблему. Например, в потоке A вызов метода соединения потока B означает **: A ожидает завершения выполнения потока B (освобождает право на выполнение ЦП), а затем продолжает выполнение. **
код показывает, как показано ниже:
public class ThreadTest {
public static void main(String[] args) {
Thread spring = new Thread(new SeasonThreadTask("春天"));
Thread summer = new Thread(new SeasonThreadTask("夏天"));
Thread autumn = new Thread(new SeasonThreadTask("秋天"));
try
{
//春天线程先启动
spring.start();
//主线程等待线程spring执行完,再往下执行
spring.join();
//夏天线程再启动
summer.start();
//主线程等待线程summer执行完,再往下执行
summer.join();
//秋天线程最后启动
autumn.start();
//主线程等待线程autumn执行完,再往下执行
autumn.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
class SeasonThreadTask implements Runnable{
private String name;
public SeasonThreadTask(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <4; i++) {
System.out.println(this.name + "来了: " + i + "次");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
результат операции:
春天来了: 1次
春天来了: 2次
春天来了: 3次
夏天来了: 1次
夏天来了: 2次
夏天来了: 3次
秋天来了: 1次
秋天来了: 2次
秋天来了: 3次
20. Какова роль LockSupport?
- роль LockSupport
- Разница между парковкой и непарковкой, а также ожиданием, уведомлением
- Роль блокатора объектов?
LockSupport — это класс инструментов, основной функцией которого является приостановка и пробуждение потоков.Этот класс инструментов является основой для создания блокировок и других классов синхронизации.
public static void park(); //挂起当前线程,调用unpark(Thread thread)或者当前线程被中断,才能从park方法返回
public static void parkNanos(Object blocker, long nanos); // 挂起当前线程,有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 挂起当前线程,直到某个时间
public static void park(Object blocker); //挂起当前线程
public static void unpark(Thread thread); // 唤醒当前thread线程
Давайте посмотрим на пример:
public class LockSupportTest {
public static void main(String[] args) {
CarThread carThread = new CarThread();
carThread.setName("劳斯劳斯");
carThread.start();
try {
Thread.currentThread().sleep(2000);
carThread.park();
Thread.currentThread().sleep(2000);
carThread.unPark();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class CarThread extends Thread{
private boolean isStop = false;
@Override
public void run() {
System.out.println(this.getName() + "正在行驶中");
while (true) {
if (isStop) {
System.out.println(this.getName() + "车停下来了");
LockSupport.park(); //挂起当前线程
}
System.out.println(this.getName() + "车还在正常跑");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void park() {
isStop = true;
System.out.println("停车啦,检查酒驾");
}
public void unPark(){
isStop = false;
LockSupport.unpark(this); //唤醒当前线程
System.out.println("老哥你没酒驾,继续开吧");
}
}
}
результат операции:
劳斯劳斯正在行驶中
劳斯劳斯车还在正常跑
劳斯劳斯车还在正常跑
停车啦,检查酒驾
劳斯劳斯车停下来了
老哥你没酒驾,继续开吧
劳斯劳斯车还在正常跑
劳斯劳斯车还在正常跑
劳斯劳斯车还在正常跑
劳斯劳斯车还在正常跑
劳斯劳斯车还在正常跑
劳斯劳斯车还在正常跑
Реализация парковки и разпарковки в LockSupport чем-то похожа на функции ожидания и уведомления. но
- парку не нужно приобретать блокировку объекта
- При прерывании парк не будет генерировать исключение InterruptedException.Вы должны сами оценить статус прерывания после парковки.
- При использовании парковки и разпарковки вам не нужно беспокоиться о тупиковой ситуации, вызванной проблемой синхронизации парковки.
- LockSupport не обязательно должен быть в синхронизированном блоке
- Unpark может разбудить указанный поток, но уведомление может только случайным образом выбрать поток для пробуждения.
Роль блокатора объектов?
Удобно видеть информацию о конкретном блокирующем объекте при дампе потока.
Нет публики
Ссылка и спасибо
- Искусство параллельного программирования на Java
- Разное Что такое ложный обмен?
- Определить количество одновременных потоков в пуле потоков на основе количества ядер ЦП.
- Использование и принцип LockSupport
- Изучение строк кеша и ложный обмен