История параллелизма
На заре компьютеров не было операционной системы, и был только один способ запускать программы — выполнять их последовательно от начала до конца. Любой ресурс будет обслуживать эту программу.Когда компьютер использует одни ресурсы, другие ресурсы будут простаивать и будут существовать.浪费资源
Случай.
Упомянутая здесь трата ресурсов относится к ситуации, когда ресурсы простаивают и используются не полностью.
Появление операционных систем привело并发性
, операционная система позволяет нашей программе запускать несколько программ одновременно, программа — это процесс, что эквивалентно одновременному запуску нескольких процессов.
операционная система представляет собой并发系统
, Параллелизм является очень важной функцией операционной системы.Операционная система имеет возможность обрабатывать и планировать несколько программ одновременно.Например, несколько устройств ввода/вывода вводятся и выводятся одновременно;устройство ввода/вывода и вычисления ЦП выполняются одновременно; несколько системных и пользовательских программ активируются для выполнения поочередно и вперемежку. В то время как операционная система координирует и распределяет процессы, операционная система также выделяет разные ресурсы для разных процессов.
Операционная система реализует одновременную работу нескольких программ, что решает проблему, которую не может решить одна программа.Есть три основных момента:
-
资源利用率
, как мы упоминали выше, в одном процессе происходит пустая трата ресурсов.Например, когда вы предоставляете разрешения для папки, программа ввода не может принимать внешние вводимые символы и может принимать внешний ввод только после предоставления разрешений. Как правило, никакая другая работа не может выполняться во время ожидания программы. Если другая программа может работать во время ожидания программы, это значительно улучшит использование ресурсов. (Ресурсы не устают) Потому что он не умеет грести~ -
公平性
, разные пользователи и программы могут использовать ресурсы на компьютере. Эффективным способом запуска является разделение временных отрезков для использования ресурсов разными программами, но важно отметить, что операционная система может определять приоритет различных процессов. Хотя каждый процесс имеет право на справедливое использование ресурсов, когда процесс освобождает ресурсы, а процесс с более высоким приоритетом захватывает ресурсы, это приводит к тому, что процесс с более низким приоритетом не может получить ресурсы, что приводит к голоданию процесса. -
便利性
, единый процесс не нуждается в общении, суть общения信息交换
, своевременный обмен информацией может избежать信息孤岛
, выполнять повторяющуюся работу; все, что можно делать одновременно, можно сделать и одним процессом, но этот метод очень неэффективен, это своего рода顺序性
из.
Однако порядковое программирование (также известное как串行编程
) ни一无是处
, преимущество последовательного программирования заключается в егоИнтуитивность и простота, Объективно говоря, последовательное программирование больше подходит для образа мышления нашего человеческого мозга, но последовательное программирование нас не устраивает,we want it more!!!. Использование ресурсов, справедливость и удобство управляют процессами наряду с线程
внешний вид.
Если вы до сих пор не понимаете разницу между процессами и потоками, то я вам объясню, исходя из своего многолетнего опыта работы с операционными системами (хвастовство, а на самом деле полгода):Процесс — это приложение, а поток — это последовательный поток в приложении..
В процессе будет несколько потоков для выполнения некоторых задач, и эти задачи могут быть одинаковыми или разными. Каждый поток имеет свой порядок выполнения.
У каждого потока есть собственное пространство стека, которое является частным для потока, а также некоторые другие внутренние и общие для потока ресурсы, как показано ниже.
В компьютерах общий стек относится к стеку, а куча относится к куче.
Потоки совместно используют общепроцессные ресурсы, такие как память и дескрипторы файлов, но каждый поток также имеет свое собственное содержимое, такое как счетчик программ, стек и локальные переменные. Ниже приведены различия между процессами и потоками, совместно использующими ресурсы.
Тема А.轻量级
Облегченность процесса выражается в том, что создание и уничтожение потоков намного меньше, чем накладные расходы процесса.
Примечание. Любые сравнения являются относительными.
В большинстве современных операционных систем поток является основной единицей планирования, поэтому наша точка зрения сосредоточена на线程
запрос.
нить
что такое многопоточность
Многопоточность означает, что вы можете запускать несколько потоков в одном приложении, мы знаем, что инструкции выполняются в ЦП, а многопоточное приложение похоже на несколько ЦП, выполняющих код приложения одновременно.
На самом деле это иллюзия.Количество потоков не равно количеству ЦП.Один ЦП будет делить квант времени ЦП между несколькими потоками и выполнять переключение между каждым потоком в пределах заданного кванта времени.Может быть выполняются разными процессорами, как показано на следующем рисунке.
Параллелизм и параллелизм
并发
Это означает, что приложение будет выполнять несколько задач, но если компьютер имеет только один ЦП, приложение не может выполнять несколько задач одновременно, но приложению необходимо выполнять несколько задач, поэтому компьютер начинает выполнять следующую задачу. не завершает текущую задачу, а только временно сохраняет состояние, выполняет переключение задач, а ЦП переключается между несколькими задачами до тех пор, пока задача не будет завершена. Как показано ниже
并行
Относится к приложению, разбивающему свои задачи на более мелкие подзадачи, которые можно обрабатывать параллельно, например, на нескольких процессорах одновременно.
Преимущества и недостатки
Разумное использование потоков — это искусство, а разумное написание точной многопоточной программы — это искусство.Если потоки используются правильно, затраты на разработку и обслуживание программы могут быть эффективно снижены.
Java хорошо реализует набор средств разработки в пользовательском пространстве и предоставляет системные вызовы в пространстве ядра для поддержки многопоточного программирования.Java поддерживает богатые библиотеки классов.java.util.concurrent
и кроссплатформенный内存模型
В то же время это также повышает порог для разработчиков.Параллелизм всегда был темой высокого уровня, но теперь он также стал необходимым качеством для основных разработчиков.
Хотя у потоков много преимуществ, очень сложно писать правильные многопоточные (параллельные) программы.Ошибки в параллельных программах часто появляются и исчезают странным образом.Когда вы думаете, что проблемы нет, она появляется,难以定位
Это особенность параллельных программ, поэтому вам необходимо иметь прочные базовые навыки параллелизма на этой основе. Итак, почему возникает параллелизм?
Почему возникает параллелизм
Быстрое развитие компьютерного мира неотделимо от быстрого развития ЦП, памяти и устройств ввода-вывода, но между этими тремя всегда была разница в скорости, как видно из иерархии памяти.
Внутри ЦП находится структура регистров, и скорость доступа к регистрам выше, чем у高速缓存
, скорость доступа к кешу выше, чем к памяти, а самая медленная — к диску.
Программа выполняется в памяти. Большинству операторов в программе требуется доступ к памяти, а некоторым также требуется доступ к устройствам ввода-вывода. Согласно теории дырявого ведра, общая производительность программы зависит от самой медленной операции, которая скорость доступа к диску.
Поскольку скорость ЦП слишком высока, чтобы воспользоваться скоростью ЦП и сбалансировать разницу в скорости между тремя, компьютерная система, операционная система и компилятор внесли свой вклад, в основном следующим образом:
- ЦП использует кеш для нейтрализации различий в скорости доступа к памяти.
- Операционная система обеспечивает планирование процессов и потоков, позволяя ЦП разделять потоки во времени при выполнении инструкций, позволяя памяти и диску непрерывно взаимодействовать.
CPU 时间片
Способность выполнять разные задачи, тем самым уравновешивая различия между тремя - Компилятор обеспечивает порядок выполнения инструкций по оптимизации, позволяя разумно использовать кеш.
Хотя мы наслаждаемся этими удобствами, мы принесли нам проблемы, пока мы обсудим, что произошло, и источник мульти-нитей будет обсуждаться.
Проблемы безопасности с потоками
Безопасность потоков очень сложна, и без принятия同步机制
В случае нескольких потоков операции выполнения в нескольких потоках часто непредсказуемы, что также является одной из проблем, связанных с несколькими потоками.Давайте приведем фрагмент кода, чтобы увидеть, в чем заключаются проблемы безопасности.
public class TSynchronized implements Runnable{
static int i = 0;
public void increase(){
i++;
}
@Override
public void run() {
for(int i = 0;i < 1000;i++) {
increase();
}
}
public static void main(String[] args) throws InterruptedException {
TSynchronized tSynchronized = new TSynchronized();
Thread aThread = new Thread(tSynchronized);
Thread bThread = new Thread(tSynchronized);
aThread.start();
bThread.start();
System.out.println("i = " + i);
}
}
После вывода этой программы вы обнаружите, что значение i каждый раз разное, что не соответствует нашему прогнозу, так почему же это происходит? Давайте сначала проанализируем запущенный процесс программы.
TSynchronized
Реализует интерфейс Runnable и определяет статическую переменнуюi
, затем вincrease
Метод увеличивается каждый раз, когда значение I, называемое циклически проведено методом его реализации. Run 1000.
проблемы с видимостью
В эпоху одноядерных ЦП все потоки совместно используют один ЦП, и проблему согласованности между кешем ЦП и памятью легко решить, и нет никакой разницы между ЦП и памятью.
Если это представить картинкой, я думаю, что это будет следующим образом
В многоядерную эпоху, из-за существования нескольких ядер, каждое ядро может запускать поток независимо, и каждый ЦП имеет свой собственный кеш.В настоящее время не так просто решить согласованность данных между кешем ЦП и памятью. Когда потоки выполняются на разных ЦП, эти потоки работают с разными кэшами ЦП.
Поскольку i является статической переменной и не защищена никакими мерами безопасности потоков, несколько потоков будут одновременно изменять значение i, поэтому мы думаем, что i не является потокобезопасным, и этот результат вызван чтением i в aThread и bThread. значения не видны друг другу, так что это связано с可见性
привести к проблемам безопасности потоков.
#### проблема атомарности
Программа, которая выглядит очень обычной, но из-за двух потоковaThread
иbThread
Попеременное выполнение давало разные результаты. Но первопричина не вызвана созданием двух потоков, многопоточность является лишь необходимым условием безопасности потоков, а окончательная первопричина проявляется вi++
эта операция.
Что случилось с этой операцией? Разве это не просто операция, которая увеличивает i ? этоi++ => i = i + 1, как это может быть проблемой?
так какi++
Не один原子性
Операция, подумайте об этом, i++ на самом деле состоит из трех шагов: прочитать значение i, выполнить операцию i + 1, а затем переназначить значение, полученное i + 1, i (записать результат в память).
Когда два потока начинают работать, каждый поток будет считывать значение i в кэш ЦП, затем выполнять операцию + 1, а затем записывать значение после + 1 в память. Поскольку каждый поток имеет свой стек виртуальной машины и счетчик программ, между ними нет обмена данными, поэтому при выполнении aThread операции +1 данные будут записываться в память, а после выполнения bThread операции +1 он также будет писать Данные записываются в память, поскольку цикл выполнения кванта времени ЦП неясен, поэтому, когда aThread не записал данные в память, bThread прочитает данные в памяти, затем выполнит операцию +1 и затем запишите обратно в память, тем самым перезаписав значение i, в результате чего усилия aThread будут напрасными.
Почему возникает проблема с вышеуказанным переключением потоков?
Давайте сначала рассмотрим порядок выполнения двух потоков при нормальных обстоятельствах (то есть без проблем с безопасностью потоков).
Видно, что когда aThread выполняет полную операцию i++, операционная система переключает поток с aThread -> bThread, что является наиболее идеальной операцией.读取/增加/写入
На этапе происходит переключение потоков, что вызовет проблемы с безопасностью потоков. Например, как показано ниже
В начале i = 0 в памяти, aThread считывает значение в памяти и считывает его в свой регистр, и выполняет операцию +1. В это время происходит переключение потока, и bThread начинает выполнять и читать значение в памяти и прочитать его в свой регистр, в это время происходит переключение потока, поток переключается на aThread, чтобы начать работу, aThread записывает значение своего собственного регистра обратно в память, и в это время снова происходит переключение потока , из aThread -> bThread поток bThread прибавляет 1 к значению собственного регистра и потом записывает обратно в память.После записи значение в памяти не 2, а 1, и значение i в памяти равно перезаписано.
мы упоминали выше原子性
Эта концепция, то что такое атомарность?
Атомарная операция параллельного программирования — это операция, которая выполняется полностью независимо от любого другого процесса.Атомарные операции в основном используются в современных операционных системах и системах параллельной обработки.
Атомарные операции часто используются в ядре, поскольку ядро является основным компонентом операционной системы. Однако большинство компьютерного оборудования, компиляторов и библиотек также обеспечивают атомарные операции.
При загрузке и сохранении компьютерное оборудование считывает и записывает слова памяти. Чтобы сопоставить, увеличить или уменьшить значение, это обычно выполняется с помощью атомарных операций. Во время атомарной операции процессор может выполнять операции чтения и записи во время одной и той же передачи данных. Таким образом, другие механизмы ввода/вывода или процессоры не могут выполнять задачи чтения или записи памяти, пока атомарная операция не будет завершена.
Проще говоря, этоАтомарные операции либо выполняют все, либо ни одну из них. Атомарность транзакций базы данных также развивается на основе этой концепции.
проблема с заказом
В параллельном программировании это также приносит много головной боли.有序性
Проблема упорядоченности, как следует из названия, заключается в последовательности, которая относится к порядку, в котором инструкции выполняются в компьютере. Очень очевидный пример в JVM类加载
Это схема процесса загрузки классов JVM, также известная как жизненный цикл класса. Класс проходит пять этапов от загрузки в JVM до выгрузки.Загрузить, подключить, инициализировать, использовать, выгрузить. Порядок выполнения этих пяти процессов определен, но на этапе подключения он также будет разделен на три процесса, а именноПроверить, подготовить, разобратьЭтапы, порядок выполнения этих трех этапов не является детерминированным, обычно они чередуются, и во время выполнения одного этапа активируется другой этап.
Проблема упорядочения обычно возникает из-за компилятора, и компилятор иногда это делает.Делайте плохие вещи с добрыми намерениями, он часто заменяет порядок выполнения инструкций, чтобы оптимизировать производительность системы.
проблема активности
Многопоточность также приносит活跃性
Вопрос, как вы определяете проблему деятельности? Вопросы деятельности сосредоточены напроизойдет ли что-то.
Взаимная блокировка возникает, если каждый поток в группе потоков ожидает возникновения события, которое может быть вызвано только ожидающими потоками в группе..
Проще говоря, каждый поток ожидает, пока другие потоки высвободят ресурсы, а другие ресурсы также ждут, пока каждый поток высвободит ресурсы, так что ни один поток не освободит свои собственные ресурсы первым, что приведет к взаимоблокировке, и все потоки будут ждать бесконечно долго. .
Необходимые условия взаимоблокировки
Есть четыре причины взаимоблокировки, устранение одной из них может устранить взаимоблокировку.
- Условие взаимного исключения: относится к исключительному использованию выделенных ресурсов процессом, то есть определенный ресурс занят только одним процессом в течение определенного периода времени. Если в это время есть другие процессы, запрашивающие ресурсы, запрашивающая сторона может только ждать, пока процесс, занимающий ресурсы, будет освобожден.
- Условия запроса и удержания: относится к процессу, который удерживал хотя бы один ресурс, но был сделан новый запрос ресурса, и ресурс был занят другими процессами.В это время запрашивающий процесс заблокирован, но он сохраняет владение другие ресурсы, которые он получил.
- Условие отсутствия депривации: относится к ресурсу, полученному процессом, его нельзя лишить до того, как он будет израсходован, и он может быть освобожден сам по себе только тогда, когда он израсходован.
- Циклическое ожидание: при возникновении взаимоблокировки должна существовать циклическая цепочка, соответствующая процессу.
Другими словами, каждый поток в наборе заблокированных потоков ожидает ресурса, удерживаемого другим заблокированным потоком. Но поскольку все потоки не могут работать, ни один из них не может освободить ресурсы, поэтому ни один поток не может быть разбужен.
Если тупик очень痴情
, тогда活锁
выразить в предложении弄巧成拙
.
В некоторых случаях, когда поток понимает, что он не может получить следующую необходимую ему блокировку, он попытается вежливо освободить уже полученную блокировку, а затем подождет очень короткое время, чтобы попытаться получить ее снова. Представьте себе такую сцену: когда два человека встречаются на узкой дороге, они оба хотят уступить друг другу дорогу, и одинаковый темп будет мешать обеим сторонам двигаться вперед.
Теперь представьте пару параллельных потоков, использующих два ресурса. После того, как их соответствующие попытки получить другую блокировку потерпят неудачу, оба потока снимут блокировки, которые они удерживают, и попытаются снова, и процесс повторится. Очевидно, что в этом процессе нет блокировки потока, но поток все равно не будет выполняться вниз, эту ситуацию мы называем活锁(livelock)
.
Проблемы с живучестью возникают, если что-то, чего мы ожидаем, никогда не происходит, например, бесконечный цикл в одном потоке.
while(true){...}
for(;;){}
В многопоточности, например, aThread и bThread нуждаются в определенных ресурсах, aThread продолжает занимать ресурсы и не освобождает их, а bThread не может выполняться все время, что вызовет проблемы с активностью, а потоки bThread будут генерировать饥饿
, о чем мы поговорим позже.
проблемы с производительностью
С проблемой деятельности тесно связана проблема性能
Проблема, если проблема деятельности связана с конечным результатом, то проблема производительности связана с процессом создания результата.Есть много аспектов проблем производительности: например,Время обслуживания слишком велико, пропускная способность слишком низкая, а потребление ресурсов слишком велико.Такие проблемы в многопоточности тоже бывают.
В многопоточности есть очень важный фактор производительности, о котором мы упоминали выше.线程切换
, также известен как上下文切换(Context Switch)
, эта операция дорогая.
В компьютерном мире иностранцы любят использовать слово контекст, которое охватывает много контента, включая ресурсы переключения контекста, состояние регистров, счетчики программ и так далее. Переключение контекста обычно относится к ресурсам, состояниям регистров, изменениям счетчика программ и т. д. этих переключений контекста.
При переключении контекста контекст сохраняется и восстанавливается, локальность теряется, и много времени тратится на переключение потоков вместо их выполнения.
Почему переключение потоков такое дорогое? Переключение между потоками включает следующие шаги
Переключение ЦП с одного потока на другой включает в себя приостановку текущего потока, сохранение его состояния, например регистров, а затем восстановление состояния потока для переключения, загрузку нового счетчика программ, после чего переключение потока фактически завершено; В этот момент ЦП не выполняет код переключения потока, который, в свою очередь, выполняет новый код, связанный с потоком.
Несколько способов вызвать переключение потоков
Переключение между потоками обычно является проблемой, которую необходимо рассматривать на уровне операционной системы, так как же можно вызвать переключение контекста потока? Или каковы стимулы для переключения потоков? Существуют в основном следующие способы вызвать переключение контекста
- Текущая выполняемая задача завершена, и ЦП системы обычно планирует выполнение следующего потока.
- Текущая выполняемая задача сталкивается с блокирующими операциями, такими как ввод-вывод, и планировщик потоков приостанавливает эту задачу и продолжает планировать следующую задачу.
- Несколько задач одновременно вытесняют ресурсы блокировки Текущая задача не получает ресурсы блокировки, приостанавливается планировщиком потоков и продолжает планировать следующую задачу.
- Пользовательский код приостанавливает текущую задачу, например, поток, выполняющий метод сна, освобождая ЦП.
- Используйте аппаратные прерывания для переключения контекста
потокобезопасность
В Java для обеспечения безопасности потоков мы должны правильно использовать потоки и блокировки, но это всего лишь один из способов обеспечить безопасность потоков.Чтобы написать правильный потокобезопасный код, ядром является управление операциями доступа к состоянию. Самое важное это самое共享(Shared)
и可变(Mutable)
статус. Только общие и изменяемые переменные будут иметь проблемы, частные переменные не будут иметь проблем, см.程序计数器
.
Состояние объекта можно понимать как данные, хранящиеся в переменных экземпляра или статических переменных, совместное использование означает, что к переменной могут обращаться несколько потоков одновременно, а изменяемость означает, что переменная будет изменяться в течение своего жизненного цикла. Является ли переменная потокобезопасной, зависит от того, используется ли к ней доступ несколькими потоками. Чтобы обеспечить безопасный доступ к переменной, переменная должна быть изменена с помощью механизма синхронизации.
Если механизм синхронизации не используется, то необходимо избегать многопоточного доступа к общим переменным, в основном есть два пути:
- Не разделяйте переменные между несколькими потоками
- Сделать общие переменные неизменяемыми
Мы много раз говорили о безопасности потоков, так что же такое безопасность потоков?
Что такое потокобезопасность
Код, который несколько потоков могут безопасно вызывать одновременно, называется потокобезопасным, если фрагмент кода безопасен, то этот фрагмент кода не существует.竞态条件
. Условия гонки возникают только тогда, когда несколько потоков совместно используют ресурсы.
На основании вышеизложенного можно сделать простой вывод:Когда к классу обращаются несколько потоков, класс всегда демонстрирует правильное поведение, тогда говорят, что класс является потокобезопасным..
Один поток — это многопоток с числом потоков, равным 1, и один поток должен быть потокобезопасным. Чтение значения переменной не представляет проблемы безопасности, поскольку значение переменной не будет изменено, сколько бы раз оно ни читалось.
атомарность
Мы упомянули концепцию атомарности выше, вы можете поставить原子性
Операция представляет собой превращение不可分割
В целом у него всего два результата, либо все выполняются, либо все откатываются. Вы можете думать об атомарности как婚姻关系
Один из них, мужчины и женщины, дают только два результата,好好的
и说散就散
, жизнь среднего человека можно рассматривать как некую атомарность, конечно не исключаем时间管理(线程切换)
Например, мы знаем, что переключение потоков обязательно будет сопровождаться проблемами безопасности. Люди, выходящие на волны, также приведут к двум результатам. Эти два результата соответствуют двум результатам безопасности: безопасность потоков (хорошо) и небезопасность потоков (скажем, это уйдет).
состояние гонки
С помощью описанных выше навыков переключения потоков можно легко определить состояние гонки, которое относится кКогда два или более потока изменяют общие данные одновременно, что влияет на корректность программы, это называется состоянием гонки., переключение потоков является побудительным фактором, вызывающим состояние гонки, проиллюстрируем на примере, посмотрим на кусок кода
public class RaceCondition {
private Signleton single = null;
public Signleton newSingleton(){
if(single == null){
single = new Signleton();
}
return single;
}
}
В приведенном выше коде задействовано состояние гонки, которое заключается в том, чтобы судитьsingle
В это время, если сингл оценивается как пустой, в это время произошло переключение потока, и выполняется другой поток.При оценке сингла он также пуст, и выполняется новая операция, а затем поток переключается обратно на предыдущий поток, а затем выполняется новая операция, тогда в памяти будет два объекта Singleton.
запорный механизм
В Java существует множество способов блокировки и защиты общих и изменяемых ресурсов. Java предоставляет встроенный механизм защиты ресурсов:synchronized
ключевое слово, имеющее три механизма защиты
- Заблокируйте метод, чтобы гарантировать, что только один из нескольких потоков выполняет метод;
- Заблокируйте экземпляр объекта (в нашем обсуждении выше переменные можно заменить объектами), чтобы гарантировать, что только один поток из множества потоков может получить доступ к экземпляру объекта;
- Заблокируйте объект класса, чтобы гарантировать, что только один поток может получить доступ к ресурсам в классе для нескольких потоков.
Блок кода, в котором ключевое слово synchronized защищает ресурсы, широко известен как同步代码块(Synchronized Block)
,Например
synchronized(lock){
// 线程安全的代码
}
Каждый объект Java можно использовать как блокировку, реализующую синхронизацию, эти блокировки называются内置锁(Instrinsic Lock)
или监视器锁(Monitor Lock)
. Поток автоматически получает блокировку перед входом в код синхронизации и автоматически снимает блокировку при выходе из кода синхронизации, и независимо от того, выходит ли он через обычный путь выполнения или путь исключения, единственный способ получить встроенную блокировку — это введите этот защищенный блокировкой блок кода синхронизации или метод.
Другой неявной семантикой синхронизированного является互斥
, взаимное исключение означает独占
, блокировку удерживает не более одного потока. Когда поток A пытается получить блокировку, удерживаемую потоком B, поток A должен ждать или блокироваться до тех пор, пока поток B не снимет блокировку. Если поток B не снимает блокировку, поток A будет продолжать ждать .
Когда поток A получает блокировку, удерживаемую потоком B, поток A должен ждать или блокироваться, но поток B, который получает блокировку, может повторно входить, и значение повторного входа может быть представлено фрагментом кода.
public class Retreent {
public synchronized void doSomething(){
doSomethingElse();
System.out.println("doSomething......");
}
public synchronized void doSomethingElse(){
System.out.println("doSomethingElse......");
}
Поток, который получает блокировку метода doSomething(), может выполнить метод doSomethingElse() и может повторно выполнить содержимое метода doSomething() после выполнения. Блокировка повторного входа также поддерживает повторный вход между подклассами и родительскими классами, о чем мы расскажем позже.
volatile
легкийsynchronized
, Это легкий способ блокировки, изменчивая видимость объекта, который будет заблокирован со стороны, обеспечивая общие переменные. Видимость означает, что когда поток изменяет общую переменную, другой поток может看见
Это измененное значение. volatile дороже в исполнении, чемsynchronized
Гораздо ниже, потому что volatile не вызывает переключения контекста потока.
мы также можем использовать原子类
Для обеспечения безопасности потоков атомарный класс фактическиrt.jar
Ниже сatomic
класс, начиная с
Кроме того, мы также можем использоватьjava.util.concurrent
Поточно-безопасный класс коллекции в наборе инструментов обеспечивает потокобезопасность Конкретный класс реализации и его принцип будут обсуждаться позже.
Параллельные системы могут быть реализованы с использованием различных моделей параллелизма, которые описывают, как потоки в системе взаимодействуют для выполнения параллельных задач. Различные модели параллелизма разделяют задачи по-разному, и потоки могут общаться и взаимодействовать по-разному.
Условия гонки и критические регионы
Состояние гонки — это особое состояние, возникающее в критической области кода. Критическая область — это часть кода, которая выполняется несколькими потоками одновременно, и порядок выполнения кода в критической области приведет к различным результатам. Состояние гонки возникает, когда несколько потоков выполняют критическую часть кода, что приводит к разным результатам в зависимости от порядка выполнения.
Модели параллелизма очень похожи на распределенные системы.
Модель параллелизма на самом деле очень похожа на модель распределенной системы.线程
общаться друг с другом, в то время как в модели распределенной системы进程
общаться друг с другом. Однако по сути процессы и потоки также очень похожи. Вот почему параллельная модель и распределенная модель очень похожи.
Распределенные системы обычно сталкиваются с большим количеством проблем и проблем, чем параллельные системы, такие как взаимодействие процессов, сетевые исключения или удаленные зависания машины и т. д. Но параллельная модель также сталкивается с такими проблемами, как сбой процессора, проблемы с сетевой картой, проблемы с жестким диском и т. д.
Поскольку модель параллелизма и распределенная модель похожи, они могут учиться друг у друга, например, модель, используемая для распределения потоков, похожа на модель балансировки нагрузки в распределенной системной среде.
На самом деле, говоря прямо, идея распределенной модели является производной от дедукции и развития модели параллелизма.
признать два государства
Важным аспектом модели параллелизма является то, должны ли потоки共享状态
Да, это共享状态
все еще独立状态
. Общее состояние также означает, что некоторое состояние является общим для разных потоков.
Государство на самом деле数据
, например один или несколько объектов. Когда потоки хотят обмениваться данными, это вызовет竞态条件
или死锁
И другие вопросы. Конечно, эти проблемы только возможны, и конкретная реализация зависит от того, безопасно ли вы используете и получаете доступ к общим объектам.
Независимое состояние означает, что состояние не используется несколькими потоками. Если потокам необходимо обмениваться данными, они могут получить доступ к неизменяемым объектам для достижения этой цели. Это наиболее эффективный способ избежать проблем параллелизма, как показано на следующем рисунке.
Использование независимого состояния делает наш дизайн проще, потому что только один поток может получить доступ к объекту, который неизменен, даже если он заменяется.
Модель параллелизма
Параллельный рабочий
Первая модель параллелизма — это параллельная рабочая модель, в которой клиент передает задачи代理人(Delegator)
, а затем распределить работу по разным工人(worker)
. Как показано ниже
Основная идея параллельного воркера состоит в том, что он в основном имеет два процесса, а именно агент и воркер.Делегатор отвечает за получение задач от клиента и доставку задач конкретному воркеру для обработки.После того, как воркер завершит обработку , он возвращает результат Делегатору.После того, как Делегатор получает результаты, обработанные Воркером, он агрегирует их и доставляет клиенту.
Модель параллельного рабочего процесса — очень распространенная модель в модели параллелизма Java. многиеjava.util.concurrent
Все инструменты параллелизма в пакете используют эту модель.
Преимущества параллельных рабочих
Очень очевидная особенность модели параллельных рабочих процессов заключается в том, что ее легко понять.Чтобы улучшить параллелизм системы, вы можете добавить несколько рабочих для выполнения задач.
Еще одним преимуществом параллельной модели Worker является то, что она разбивает задачу на несколько небольших задач и выполняет их одновременно.Получив результат обработки Worker, Delegator вернет его клиенту. Весь процесс Worker -> Delegator -> Client да异步
из.
Недостатки параллельных рабочих
Точно так же шаблон Parallel Worker также имеет некоторые скрытые недостатки.
Общее состояние может быть сложным
Реальный параллельный рабочий процесс сложнее, чем мы его себе представляли, главным образом потому, что параллельный рабочий процесс обычно обращается к некоторым общим данным в памяти или общей базе данных.
Эти общие состояния могут использовать некоторые рабочие очереди для хранения бизнес-данных, кэшей данных, пулов соединений с базами данных и т. д. При взаимодействии потоков потоки должны гарантировать, что общее состояние может использоваться другими потоками, а не просто оставаться в кеше ЦП, чтобы сделать себя доступными.Конечно, это все проблемы, которые программисты должны учитывать при проектировании. Тредов нужно избегать竞态条件
,死锁
и многие другие проблемы параллелизма, вызванные общим состоянием.
Когда несколько потоков обращаются к общим данным, параллелизм теряется, поскольку операционная система должна гарантировать, что только один поток может получить доступ к данным, что может привести к конфликтам и вытеснению общих данных. Потоки, которые не вытесняют ресурсы,阻塞
.
Современные неблокирующие параллельные алгоритмы могут уменьшить конкуренцию и повысить производительность, но неблокирующие алгоритмы сложнее реализовать.
可持久化的数据结构(Persistent data structures)
это еще один вариант. Постоянная структура данных всегда сохраняет предыдущую версию после модификации. Следовательно, если несколько потоков изменяют постоянную структуру данных одновременно, а один поток изменяет ее, модифицирующий поток получает ссылку на новую структуру данных.
Хотя постоянные структуры данных являются новым решением, с этим подходом возникают некоторые проблемы.Например, постоянный список будет добавлять новые элементы в начало списка и возвращать ссылку на добавленный новый элемент, но другие потоки по-прежнему будут хранить только ссылка на предыдущий первый элемент в списке, они не видят вновь добавленный элемент.
Постоянные структуры данных, такие как链表(LinkedList)
Низкая производительность оборудования. Каждый элемент в списке является объектом, и эти объекты разбросаны по памяти компьютера. Последовательный доступ на современных процессорах, как правило, намного быстрее, поэтому использование структур данных с последовательным доступом, таких как массивы, может повысить производительность. Кэш ЦП может загружать в кеш большой блок матриц и предоставлять ЦП доступ к данным в кеше ЦП сразу после загрузки. Для связанных списков практически невозможно разнести элементы по всей оперативной памяти.
работник без гражданства
Общее состояние может быть изменено другими потоками, поэтому рабочие должны повторно читать общее состояние каждый раз, когда они работают с ним, чтобы обеспечить правильную работу с репликами. Рабочий процесс, который не поддерживает состояние внутри потока, становится рабочим процессом без сохранения состояния.
Порядок работы не определен
Еще одним недостатком модели параллельной работы является то, что порядок заданий не определен, и нет гарантии, какие задания будут выполняться первыми или последними. Задача A назначается рабочему перед задачей B, но задача B может выполняться перед задачей A.
сборочная линия
Вторая модель параллелизма — это то, с чем мы часто сталкиваемся в производственной среде.流水线并发模型
, ниже приведена блок-схема модели проектирования трубопровода.
Эта организационная структура подобна рабочему на конвейере на заводе: каждый рабочий выполняет только часть всей работы, после завершения которой рабочий передает работу следующему рабочему.
Каждая программа работает в своем собственном потоке и не делится состоянием друг с другом.Эта модель также известна как модель параллелизма без общего доступа.
Использование конвейерной модели параллелизма обычно разрабатывается как非阻塞I/O
, то есть когда на работника не назначены никакие задачи, работник будет выполнять другую работу. Неблокирующий ввод-вывод означает, что когда рабочий процесс начинает операцию ввода-вывода, например чтение файла из сети, рабочий процесс не ждет завершения вызова ввода-вывода. Поскольку операции ввода-вывода выполняются медленно, ожидание ввода-вывода занимает очень много времени. В ожидании ввода-вывода ЦП может заниматься другими делами, а результат после завершения операции ввода-вывода будет передан следующему рабочему процессу. Ниже приведена блок-схема неблокирующего ввода-вывода.
На практике задачи обычно не проходят по сборочной линии, поскольку большинству программ нужно делать много вещей, они должны перемещаться между разными рабочими в соответствии с различной выполненной работой, как показано на следующем рисунке.
Задача также может потребовать участия нескольких рабочих в завершении.
Реактивная система, управляемая событиями
Системы, использующие конвейерную модель, иногда называют响应式
или事件驱动系统
, эта модель будет реагировать в соответствии с внешними событиями, событием может быть HTTP-запрос или файл был загружен в память.
Модель актера
В модели Актера каждый Актер на самом деле является Рабочим, и каждый Актер может выполнять задачи.
Проще говоря, акторная модель — это модель параллелизма, которая определяет набор общих правил поведения и взаимодействия системных компонентов.Самым известным языком программирования, использующим этот набор правил, является Erlang. УчастникActor
Отвечая на полученные сообщения, можно создать больше участников или отправить больше сообщений, готовясь к получению следующего сообщения.
Модель каналов
В модели канала работники обычно не общаются напрямую, вместо этого они обычно отправляют события для разных通道(Channel)
Затем другие работники могут получать сообщения по этим каналам. Ниже приведена схема модели канала.
Иногда воркерам не нужно знать, кто следующий воркер, им нужно только написать автора в канал, а воркеры, слушающие канал, могут подписаться или отписаться, что снижает связь между воркерами и воркерами.
Преимущества конструкции трубопровода
По сравнению с моделью параллельного проектирования модель конвейера имеет некоторые преимущества, конкретные преимущества заключаются в следующем.
Не будет общего состояния
Поскольку конструкция конвейера может гарантировать, что рабочий процесс будет передан следующему рабочему процессу после завершения обработки, нет необходимости обмениваться каким-либо состоянием между рабочими процессами и нет необходимости учитывать проблемы параллелизма. Вы даже можете реализовать каждого работника как отдельный поток.
условный работник
Рабочие в конвейерной конструкции имеют состояние, потому что они знают, что никакой другой поток не изменяет их собственные данные.С состоянием означает, что они могут хранить данные, с которыми им нужно работать, в памяти.С состоянием, как правило, быстрее, чем без состояния.
лучшая интеграция оборудования
Потому что конвейер можно представить как однопоточный, а преимущество однопоточной работы в том, что он может работать так же, как и аппаратное обеспечение. Поскольку рабочие процессы с отслеживанием состояния обычно кэшируют данные в ЦП, это обеспечивает более быстрый доступ к кэшированным данным.
сделать задачи более эффективными
Задачи в модели параллелизма конвейера могут быть упорядочены и обычно используются для записи и восстановления журнала.
Недостатки конструкции трубопровода
Недостаток конвейерной модели параллелизма заключается в том, что в задачах участвует несколько рабочих, и поэтому они могут быть распределены по нескольким классам в коде проекта. Поэтому трудно определить, какую задачу выполняет каждый работник. Конвейерную обработку также сложнее кодировать, разработка кода со многими вложенными обработчиками обратного вызова часто называется回调地狱
. Ад обратного вызова трудно отследить для отладки.
функциональный параллелизм
Функциональная параллельная модель — это недавно предложенная модель параллелизма, и ее основная идея заключается в использовании вызовов функций для достижения цели. Передача сообщения эквивалентна вызову функции. Параметры, передаваемые функции, копируются, поэтому никакая сущность вне функции не может манипулировать данными внутри функции. Это заставляет функцию выполнять что-то вроде原子
работать. Каждый вызов функции может выполняться независимо от любого другого вызова функции.
Когда каждый вызов функции выполняется независимо, каждая функция может выполняться на отдельном процессоре. Другими словами, функциональный параллелизм эквивалентен независимому выполнению каждым процессором своих собственных задач.
в JDK 1.7ForkAndJoinPool
Класс реализует функциональный параллелизм. Java 8 предлагает концепцию потока, а также можно перебирать большое количество коллекций, используя параллельные потоки.
Сложность функционального параллелизма заключается в том, чтобы знать поток вызовов функций и какие ЦП выполняют какие функции.Вызовы кросс-процессорных функций принесут дополнительные накладные расходы.
Как мы уже говорили, поток — это поток в процессе.顺序流
, В Java каждый поток Java подобен потоку последовательности JVM, выполняя код как виртуальный ЦП. на Явеmain()
Метод — это специальный поток, а основной поток, созданный JVM, — это主执行线程
, в Java методы инициируются основным методом. В основном методе вы также можете создавать другие线程
(поток выполнения), эти потоки могут выполнять код приложения вместе с основным методом.
Поток Java также является объектом, как и любой другой объект. Thread в Java означает поток, Threadjava.lang.Thread
Экземпляр класса или его подклассов. Итак, давайте обсудим, как создавать и запускать потоки в Java.
Создать и запустить поток
В Java существует три основных способа создания потоков.
- по наследству
Thread
класс для создания потока - путем реализации
Runnable
интерфейс для создания потоков - пройти через
Callable
иFuture
создать нить
Давайте обсудим эти методы создания отдельно
Наследовать от класса Thread для создания потоков
Первый способ — наследовать класс Thread для создания потоков, как показано в следующем примере.
public class TJavaThread extends Thread{
static int count;
@Override
public synchronized void run() {
for(int i = 0;i < 10000;i++){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
TJavaThread tJavaThread = new TJavaThread();
tJavaThread.start();
tJavaThread.join();
System.out.println("count = " + count);
}
}
Основные шаги для создания потока следующие:
- Определите класс потока, чтобы наследовать класс Thread и переопределить метод запуска.Внутри метода запуска находится задача, которая должна быть выполнена потоком, поэтому метод запуска также называется
执行体
- Создал подкласс Thread, подкласс в приведенном выше коде
TJavaThread
- Метод запуска требует внимания, не вызывается напрямую
run
метод для запуска потока, вместо этого используйтеstart
метод запуска потока. Конечно, метод run можно вызвать, поэтому он станет обычным вызовом метода вместо создания нового потока для вызова.
public static void main(String[] args) throws InterruptedException {
TJavaThread tJavaThread = new TJavaThread();
tJavaThread.run();
System.out.println("count = " + count);
}
В этом случае весь основной метод имеет только один поток выполнения, то есть основной поток, который меняется с двух потоков выполнения на один поток выполнения.
Конструктору Thread нужен только объект Runnable, вызовите метод start() объекта Thread для выполнения необходимых операций инициализации для потока, а затем вызовите метод run Runnable для запуска задач в этом потоке. Мы использовали тему вышеjoin
метод, который используется для ожидания завершения выполнения потока.Если мы не добавим метод соединения, он не будет ждать завершения выполнения tJavaThread, и результат вывода может быть не10000
Как видите, run возвращается до завершения метода run. То есть программа не будет ждать завершения метода run перед выполнением следующих инструкций.
Преимущества использования наследования для создания потоков: относительно просто написать; вы можете использоватьthis
ключевое слово указывает непосредственно на текущий поток без использованияThread.currentThread()
чтобы получить текущий поток.
Недостатки использования наследования для создания потоков: в Java разрешено только одиночное наследование (отказ от принципа использования внутренних классов для достижения множественного наследования), поэтому с помощью наследования подклассы больше не могут наследовать другие классы.
Используйте интерфейс Runnable для создания потоков
И наоборот, вы также можете использоватьRunnable
интерфейс для создания потока, следующий пример
public class TJavaThreadUseImplements implements Runnable{
static int count;
@Override
public synchronized void run() {
for(int i = 0;i < 10000;i++){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new TJavaThreadUseImplements()).start();
System.out.println("count = " + count);
}
}
Основные шаги для создания потока следующие:
- Сначала определите интерфейс Runnable и перепишите метод запуска интерфейса Runnable.Тело метода метода запуска также является телом выполнения потока потока.
- Чтобы создать экземпляр потока, вы можете использовать приведенный выше код, чтобы создать его простым способом, или вы можете создать его новым из экземпляра потока, как показано ниже.
TJavaThreadUseImplements tJavaThreadUseImplements = new TJavaThreadUseImplements();
new Thread(tJavaThreadUseImplements).start();
- Затем вызовите метод запуска объекта потока, чтобы запустить поток.
Поток использует реализациюRunnable
В то же время он также может реализовывать другие интерфейсы, что очень подходит для нескольких идентичных потоков для обработки одного и того же ресурса, отражая объектно-ориентированную идею.
Недостатком реализации Runnable является то, что программирование немного громоздко, если вы хотите получить доступ к текущему потоку, вы должны использоватьThread.currentThread()
метод.
Используйте интерфейс Callable для создания потоков
Интерфейс Runnable выполняет независимые задачи. Интерфейс Runnable не генерирует никакого возвращаемого значения. Если вы хотите иметь возможность возвращать значение после завершения задачи, вы можете реализоватьCallable
интерфейс вместо интерфейса Runnable. В Java SE5 появился интерфейс Callable, пример которого выглядит следующим образом.
public class CallableTask implements Callable {
static int count;
public CallableTask(int count){
this.count = count;
}
@Override
public Object call() {
return count;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask((Callable<Integer>) () -> {
for(int i = 0;i < 1000;i++){
count++;
}
return count;
});
Thread thread = new Thread(task);
thread.start();
Integer total = task.get();
System.out.println("total = " + total);
}
}
Я думаю, что вы уже знаете преимущества использования Callble Interface. Вы можете реализовать несколько интерфейсов и получить возвращаемое значение результата выполнения. Существуют еще некоторые различия между Callable и Runnable интерфейсами, основные различия являются следующими
- Задачи, выполняемые Callable, имеют возвращаемое значение, а задачи, выполняемые Runnable, не имеют возвращаемого значения.
- Метод Callable (переопределенный) — это метод вызова, а метод Runnable (переопределенный) — метод запуска.
- Метод call может генерировать исключения, в то время как метод Runnable не может генерировать исключения.
Используйте пул потоков для создания потоков
Во-первых, давайте познакомимся с интерфейсом верхнего уровняExecutor
, Executor не является одним из традиционных методов создания потоков, но он стал заменой для создания потоков.Преимущества использования пулов потоков заключаются в следующем.
- Использование пула потоков для повторного использования потока, управления максимальным и числом.
- Реализовать очередь потоков задач
缓存策略
и拒绝机制
. - Реализовать некоторые функции, связанные со временем, такие как выполнение по времени, периодическое выполнение и т. д.
- Изолируйте среду потока. Например, если служба транзакций и служба поиска находятся на одном сервере и соответственно открыты два пула потоков, потребление ресурсов потоком транзакций, очевидно, больше, поэтому при настройке независимого пула потоков более медленная служба транзакций и службы поиска разделены. Избегайте потоков служб, влияющих друг на друга.
Вы можете заменить создание потока чем-то вроде
new Thread(new(RunnableTask())).start()
// 替换为
Executor executor = new ExecutorSubClass() // 线程池实现类;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
ExecutorService
Это реализация Executor по умолчанию и расширенный интерфейс Executor Класс ThreadPoolExecutor предоставляет расширенную реализацию пула потоков.Executors
Класс предоставляет удобные фабричные методы для этих исполнителей. Вот несколько способов создания потоков с помощью ExecutorService.
CachedThreadPool
Это упрощает параллельное программирование. Executor обеспечивает уровень косвенности между клиентом и задачей; вместо того, чтобы клиент выполнял задачу напрямую, этот промежуточный объект будет выполнять задачу. Executor позволяет управлять异步
Выполнение задачи без явного управления жизненным циклом потока.
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++){
service.execute(new TestThread());
}
service.shutdown();
}
CachedThreadPool
Для каждой задачи создается поток.
Примечание. Объект ExecutorService является статическим с использованием
Executors
Созданный, этот метод может определить тип Исполнителя. правильноshutDown
Вызов предотвращает отправку новых задач в ExecutorService , который завершает работу после завершения всех задач в Executor.
FixedThreadPool
FixedThreadPool позволяет использовать有限
набор потоков для запуска многопоточности
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 0;i < 5;i++){
service.execute(new TestThread());
}
service.shutdown();
}
С FixedThreadPool вы можете предварительно выполнить выделение дорогостоящих потоков за один раз, тем самым ограничивая количество потоков. Это экономит время, потому что вам не нужно оплачивать накладные расходы на фиксированное создание потоков для каждой задачи.
SingleThreadExecutor
SingleThreadExecutor — это线程数量为 1
Для FixedThreadPool, если несколько задач отправляются в SingleThreadPool одновременно, эти задачи будут поставлены в очередь, каждая задача завершится до запуска следующей задачи, и все задачи будут использовать один и тот же поток. SingleThreadPool сериализует все переданные ему задачи и будет поддерживать свою собственную (скрытую) висячую очередь.
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for(int i = 0;i < 5;i++){
service.execute(new TestThread());
}
service.shutdown();
}
Как видно из вывода, задачи выполняются рядом друг с другом. Я выделил пять потоков для задачи, но эти пять потоков не имеют эффекта переключения между входами и выходами, как мы видели ранее, он каждый раз сначала выполняет свой собственный поток, а затем остальные потоки продолжают работу.走完
Путь выполнения этого потока. Вы можете использовать SingleThreadExecutor, чтобы убедиться, что в любой момент времени выполняется только одна задача.
впадать в спячку
Простой способ повлиять на поведение задачи — перевести поток в спящий режим, выбрать заданное время ожидания и вызвать его.sleep()
метод, широко используемыйTimeUnit
На этот раз класс заменяетThread.sleep()
метод, пример выглядит следующим образом:
public class SuperclassThread extends TestThread{
@Override
public void run() {
System.out.println(Thread.currentThread() + "starting ..." );
try {
for(int i = 0;i < 5;i++){
if(i == 3){
System.out.println(Thread.currentThread() + "sleeping ...");
TimeUnit.MILLISECONDS.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "wakeup and end ...");
}
public static void main(String[] args) {
ExecutorService executors = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++){
executors.execute(new SuperclassThread());
}
executors.shutdown();
}
}
Сравнение метода sleep() в TimeUnit и метода Thread.sleep() см. в следующем блоге.
приоритет
Выше упоминалось, что выполнение каждого потока планировщиком потоков непредсказуемо и выполняется случайным образом, поэтому есть ли способ сообщить планировщику потоков, какая задача должна быть выполнена первой? Вы можете сообщить планировщику потоков, какой поток имеет более высокий приоритет выполнения, установив состояние приоритета потока,Пожалуйста, отправьте заказ этому райдеру прямо сейчас, планировщик потоков имеет тенденцию позволять потокам с более высоким приоритетом выполняться первыми, однако это не означает, что потоки с более низким приоритетом не будут выполняться, то есть приоритет не вызовет проблем взаимоблокировки. Потоки с более низким приоритетом просто выполняются реже.
public class SimplePriorities implements Runnable{
private int priority;
public SimplePriorities(int priority) {
this.priority = priority;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
for(int i = 0;i < 100;i++){
System.out.println(this);
if(i % 10 == 0){
Thread.yield();
}
}
}
@Override
public String toString() {
return Thread.currentThread() + " " + priority;
}
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++){
service.execute(new SimplePriorities(Thread.MAX_PRIORITY));
}
service.execute(new SimplePriorities(Thread.MIN_PRIORITY));
}
}
Метод toString() переопределен, поэтому с помощьюThread.toString()
метод для печати имени потока. Вы можете переопределить вывод потока по умолчанию, который используется здесь.Thread[pool-1-thread-1,10,main]вывод этой формы.
Из вывода видно, что последний поток имеет самый низкий приоритет, а остальные потоки имеют самый высокий приоритет. Обратите внимание, что приоритеты устанавливаются в начале run , установка их в конструкторе ничего не даст, поскольку к этому моменту поток еще не выполнял задачи.
Хотя JDK имеет 10 приоритетов, обычно толькоMAX_PRIORITY, NORM_PRIORITY, MIN_PRIORITYТри уровня.
идти на компромисс
Как мы упоминали выше, если вы знаете, что поток почти запущен в методе run(), то он может дать подсказку планировщику потоков: я выполнил самую важную часть задачи и может быть использован другими потоками. ЦП ушел. Эта подсказка будет сделана через метод yield().
Очень важным моментом является то, что Thread.yield() рекомендуется выполнять переключение ЦП, а не принудительное переключение ЦП.
Для любого важного элемента управления или при вызове приложения вы не можете полагаться наyield()
на самом деле, метод yield() часто используется неправильно.
фоновая нить
后台(daemon)
Поток относится к потоку службы, предоставляемому в фоновом режиме во время выполнения, что не является необходимым. Когда все нефоновые потоки заканчиваются, программа останавливается, и все фоновые потоки завершаются одновременно. **Наоборот, программа не завершится, пока все еще выполняются какие-либо нефоновые потоки.
public class SimpleDaemons implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
} catch (InterruptedException e) {
System.out.println("sleep() interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
for(int i = 0;i < 10;i++){
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();
}
System.out.println("All Daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
}
В каждом цикле создается 10 потоков, и каждый поток устанавливается как фоновый поток, а затем начинает выполняться.Цикл for будет выполняться десять раз, а затем выводить информацию, а затем основной поток будет спать в течение периода времени время, а затем перестать работать. В каждом цикле выполнения будет напечатана информация о текущем потоке.После запуска основного потока программа выполняется. так какdaemon
Это фоновый поток, и он не может влиять на выполнение основного потока.
но когда ты ставишьdaemon.setDaemon(true)
При удалении while(true) будет бесконечно зацикливаться, тогда основной поток выполнял наиболее важную задачу, поэтому он будет продолжать зацикливаться и не может быть остановлен.
ThreadFactory
Объект, создающий потоки по запросу. Используйте фабрики потоков для замены встроенных интерфейсов Thread или Runnable, позволяя программам использовать специальные подклассы потоков, приоритеты и т. д. Общий способ создания
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
Метод Executors.defaultThreadFactory предоставляет более полезную простую реализацию, которая устанавливает контекст созданного потока в известное значение перед возвратом.
ThreadFactory
Является интерфейсом, у него есть только один метод — метод создания потока
public interface ThreadFactory {
// 构建一个新的线程。实现类可能初始化优先级,名称,后台线程状态和 线程组等
Thread newThread(Runnable r);
}
Давайте посмотрим на пример ThreadFactory
public class DaemonThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
public class DaemonFromFactory implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory());
for(int i = 0;i < 10;i++){
service.execute(new DaemonFromFactory());
}
System.out.println("All daemons started");
TimeUnit.MILLISECONDS.sleep(500);
}
}
Executors.newCachedThreadPool
Может принимать объект пула потоков, создавая пул потоков, который создает новые потоки по мере необходимости, но повторно использует ранее созданные потоки по мере их появления и использует предоставленную ThreadFactory для создания новых потоков при необходимости.
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
присоединиться к треду
Поток может вызывать другие потокиjoin()
метод, результатом которого является ожидание в течение определенного периода времени, пока второй поток не завершится, прежде чем выполняться нормально. Если поток вызывает метод t.join() в другом потоке t, этот поток будет приостановлен и не будет отвечать до тех пор, пока целевой поток t не завершится (он может быть возвращен как true или false с помощью t.isAlive()).
Вы также можете вызвать соединение с параметром тайм-аута, чтобы установить время истечения срока действия.По истечении времени метод соединения возвращается автоматически.
Вызов присоединиться также может быть прерван вызовом в потокеinterrupted
метод, вам нужно использовать предложение try...catch
public class TestJoinMethod extends Thread{
@Override
public void run() {
for(int i = 0;i < 5;i++){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted sleep");
}
System.out.println(Thread.currentThread() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoinMethod join1 = new TestJoinMethod();
TestJoinMethod join2 = new TestJoinMethod();
TestJoinMethod join3 = new TestJoinMethod();
join1.start();
// join1.join();
join2.start();
join3.start();
}
}
Метод join() ожидает завершения потока. Другими словами, это приводит к остановке выполнения текущего потока до тех пор, пока поток, к которому он присоединился, не завершит свою задачу.
Перехват исключения потока
Из-за природы потоков вы не можете перехватывать исключения, выходящие из потока.После того, как исключение выйдет из метода запуска задачи, оно будет распространено на консоль, если вы не предпримете специальных шагов для перехвата этого неправильного исключения в Java. 5 Раньше вы могли захватывать с помощью групп потоков, но после Java 5 вам нужно использовать Executor для решения проблемы, потому что группы потоков не являются хорошей попыткой.
Следующая задача вызовет исключение во время выполнения метода запуска, и это исключение будет выдано вне метода запуска, и основной метод не сможет его перехватить.
public class ExceptionThread implements Runnable{
@Override
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
try {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new ExceptionThread());
}catch (Exception e){
System.out.println("eeeee");
}
}
}
Чтобы решить эту проблему, нам нужно изменить способ, которым Executor генерирует потоки.Java5 предоставляет новый интерфейсThread.UncaughtExceptionHandler
, что позволяет прикрепить обработчик исключений к каждому потоку.Thread.UncaughtExceptionHandler.uncaughtException()
Вызывается, когда поток вот-вот умрет из-за того, что его не поймали.
public class ExceptionThread2 implements Runnable{
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("run() by " + t);
System.out.println("eh = " + t.getUncaughtExceptionHandler());
// 手动抛出异常
throw new RuntimeException();
}
}
// 实现Thread.UncaughtExceptionHandler 接口,创建异常处理器
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught " + e);
}
}
public class HandlerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
System.out.println(this + " creating new Thread");
Thread t = new Thread(r);
System.out.println("created " + t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("ex = " + t.getUncaughtExceptionHandler());
return t;
}
}
public class CaptureUncaughtException {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool(new HandlerThreadFactory());
service.execute(new ExceptionThread2());
}
}
В программу добавлен дополнительный механизм отслеживания для проверки того, что поток, созданный фабрикой, будет передан вUncaughtExceptionHandler
, вы можете видеть, что неперехваченное исключение передаетсяuncaughtException
захватить.
Привет, я cxuan, я написал четыре PDF-файла самостоятельно, а именно: Сводка основ Java, Сводка ядра HTTP, Основы компьютера, Сводка ядра операционной системы, я организовал их в PDF-файлы, вы можете подписаться на официальную учетную запись Java Builder, чтобы ответить на PDF для получения информации о качестве.