Это четвертая часть серии статей о многопоточности. Пожалуйста, обратите внимание на следующее:
Многопоточность Java — откуда берутся потоки?
Если вы читали предыдущие статьи о потоках, то у вас будет четкое представление о принципах реализации потоков. Теоретическая поддержка даст вам лучшее руководство для практики. Тогда в этой статье основное внимание будет уделено практике потоков и реализации потоки Краткое введение в несколько приложений.
Основное содержание этой статьи:
- Классификация резьбовой безопасности
- Как реализовать синхронизацию потоков
- оптимизация блокировки
классификация резьбовой безопасности
Потокобезопасность — это не двоичный мир, в котором есть и правда, и ложь.Если вы отсортируете по «степени безопасности» потокобезопасности, Java можно разделить на следующие категории
неизменный. Если тип данных изменен как окончательный тип, можно гарантировать его неизменность (за исключением ссылочного объекта, атрибут конечного объекта не гарантируется, гарантируется только адрес памяти). Неизменяемые объекты должны быть потокобезопасными, например класс String, который является типичным неизменяемым объектом.При вызове его методов subString(), replace() и concat() он не повлияет на исходное значение, а только вернет вновь созданное Строковый объект.
абсолютно потокобезопасный. Это определение является чрезвычайно ограничительным, и для достижения класса, независимо от среды выполнения, вызывающему объекту не нужны никакие дополнительные меры синхронизации.
Относительно потокобезопасный. Это то, что мы обычно называем потокобезопасностью. Это гарантирует, что отдельные операции над объектами являются потокобезопасными, и не требуется никаких дополнительных гарантийных работ. Однако для непрерывных вызовов в определенном порядке вызывающей стороне может потребоваться использовать дополнительные методы синхронизации, чтобы гарантировать что звонок сделан правильность.
совместимость с резьбой. Это означает, что сам объект не является потокобезопасным, и вызывающая сторона может правильно использовать методы синхронизации, чтобы гарантировать безопасное использование объекта в параллельной среде. К этой категории относятся наши часто используемые не потокобезопасные классы.
нить оппозиции. Это означает, что код нельзя использовать одновременно в многопоточной среде независимо от того, принимает ли вызывающая сторона меры синхронизации. Мы должны избегать этого.
Вышеизложенное может не различать абсолютную безопасность и относительную безопасность Мы используем пример, чтобы различать:
public class VectorTest {
private Vector<Integer> vector = new Vector<Integer>();
public void remove() {
new Thread() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
}.start();
}
public void print() {
new Thread() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println(vector.get(i));
}
}
}.start();
}
public void add(int data) {
vector.add(data);
}
public static void main(String[] args) {
VectorTest test = new VectorTest();
for (int j=0;j<100;j++){
for (int i = 0; i < 10; i++) {
test.add(i);
}
test.remove();
test.print();
}
}
}
Приведенный выше код сообщит об ошибке: ArrayIndexOutOfBoundsException.Это исключение возникает в методе печати.Когда поток удаления удаляет элемент, метод печати просто выполняет метод vector.get(), и возникает это исключение.
Мы знаем, что вектор является потокобезопасным, и его методы get(), remove(), size(), add() синхронизируются с помощью synchronize, но в случае многопоточности, если не делать дополнительную синхронизацию на вызывающий метод отключен, все еще не потокобезопасный. ЭтотТо есть речь идет об относительной потокобезопасности, она не может гарантировать, что при вызове не нужны никакие дополнительные меры синхронизации.
Как реализовать потокобезопасную синхронизацию
Синхронизация с взаимным исключением является распространенным методом гарантии правильности параллелизма. Синхронизация означает, что при одновременном доступе нескольких потоков к общим данным общие данные гарантированно будут использоваться только одним потоком одновременно. Общие средства синхронизации взаимного исключения в java:synchronize а такжеReentrantLock.
Предположительно эти два метода блокировки известны тем, кто разбирается в многопоточности. Мы не будем обсуждать конкретное использование. Давайте поговорим о различиях и конкретных сценариях двух.
Мы также упомянули синхронизацию в предыдущем тексте.Это тяжелая блокировка.Поскольку поток jvm отображается на собственный поток операционной системы, при блокировке или пробуждении потока его необходимо преобразовать из пользовательского режима в режим Эта стоимость иногда будет стоить Время превышает время выполнения кода, поэтому JVM будет использовать спин-блокировки и другие методы для выполнения коротких кодов синхронизации для некоторых кодов, чтобы избежать частого входа в состояние ядра.
синхронизация — это встроенная блокировка, предоставляемая jvm и рекомендованная jvm. Код, который она пишет, относительно прост и компактен. Только когда встроенная блокировка не может удовлетворить потребности, используйте ReentrantLock.
ReentrantLock
Итак, какие расширенные функции может предоставить ReentrantLock? Давайте посмотрим на пример;
public void synA() {
synchronized (lockA) {
synchronized (lockB) {
//doSomeThing....
}
}
}
public void synB() {
synchronized (lockB) {
synchronized (lockA) {
//doSomeThing....
}
}
}
Приведенный выше синхронизированный код склонен к взаимоблокировке, когда несколько потоков вызывают synA и synB соответственно. Чтобы избежать этого, вы можете только установить один и тот же порядок всех вызовов на момент написания. И в ReentrantLock вы можете использоватьблокировка опросаспособ избежать этой проблемы.
public void tryLockA() {
long stopTime = System.currentTimeMillis() + 10000l
while (true) {
if (lockA.tryLock()) {
try {
if (lockB.tryLock()) {
try {
// doSomeThing.....
return;
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
if (System.currentTimeMillis() > stopTime) {
return;
}
}
}
В приведенном выше методе trylock, если требуемая блокировка не может быть получена, ее можно получить путем опроса, чтобы программа могла восстановить контроль и снять полученную блокировку. Кроме того, trylock также предоставляет метод перегрузки по времени, который удобен для вас, чтобы получить блокировку в течение определенного времени.Если результат не может быть выдан в течение указанного времени, программа завершится с нулем.
ReentrantLockВ дополнение к предоставлению опросных, временных блокировок также возможно обеспечитьПрерываемые операции получения блокировки, так что оковы используются в операции получения-отмены. Он также обеспечивает операции получения блокировок, честные очереди и блокировки без блочной структуры. Эти функции значительно расширяют возможности настройки операций блокировки.
Конечно, если вы не можете использовать эти расширенные функции ReentrantLock, рекомендуется использовать синхронизированный. С точки зрения производительности, синхронизация может быть сбалансирована с помощью ReentrantLock после java6, и, согласно официальным отчетам, производительность этого аспекта будет усилена в будущем, поскольку она принадлежит встроенному свойству JVM и может выполнять некоторые оптимизации, такие как в качестве объекта блокировки потока закрытой блокировки Оптимизация устранения блокировки и увеличение детализации блокировки для устранения синхронизации блокировки и т. д. Этого трудно достичь в ReentrantLock.
оптимизация блокировки
Выше мы также узнали, что когда многопоточность конкурирует за ресурсы, она будет блокироваться и ждать других строк, которые не конкурировали, а для блокировки и пробуждения требуется планирование ядра, что слишком дорого для ограниченных ЦП, поэтому JVM будет много усилий тратится на оптимизацию блокировок для повышения эффективности выполнения.
Давайте посмотрим на общие оптимизации блокировки.
блокировка спина
В состоянии блокировки разделяемых данных есть много способов удерживать его только в течение короткого периода времени, и не стоит приостанавливать и возобновлять поток на такой короткий период времени. Тогда jvm позволит потоку, ожидающему блокировки, немного подождать, но не откажется от соответствующего времени выполнения. Таким образом, мы можем увидеть, будет ли вскоре освобожден ожидающий поток, чтобы уменьшить нагрузку на планирование потоков. Если замок занят в течение короткого времени, этот эффект очень хорош, если время слишком велико, это приведет к трате ресурсов цикла и приведет к трате ресурсов.
Адаптивная блокировка спина
Спин-блокировка не может быть обработана в зависимости от времени, в течение которого блокировка занята, и она будет введена позже. время и состояние того же замка.решено. Он станет умнее.
снятие блокировки
Устранение блокировок означает, что компилятор JVM устраняет блокировки, которые требуют синхронизации в некоторых кодах, но обнаруживаются как невозможные для конкуренции с общими данными. Обнаружение устранения блокировки в основном основано на поддержке данных из анализа побегов.Если предполагается, что в фрагменте кода все данные в куче не будут экранированы, обработайте их как данные в стеке и считайте их потоко-частными, и синхронизация Нет необходимости продолжать.
блокировка огрубления
При написании кода всегда рекомендуется, чтобы область действия синхронизированного блока была как можно меньше.Если ряд операций многократно сковывается и разблокируется на объекте, даже появляясь в теле цикла, даже если нет конкуренции потоков, это приведет к ненужной потере производительности. Затем для такого типа кода jvm расширит гранулярность своей блокировки и будет использовать только одну операцию синхронизации для этой части кода.
Легкий замок
Облегченные блокировки снижают потребление производительности по сравнению с традиционными тяжеловесными блокировками за счет взаимного исключения операционной системы без многопоточной конкуренции. Заголовок объекта в jvm разделен на две части информации.Первая часть используется для хранения текущих данных самого объекта, называемого «Mark Word», который является ключом к реализации облегченной блокировки. Другая часть используется для хранения указателя данных типа объекта области метода выполнения. При попадании кода в блок синхронизации, если объект синхронизации не заблокирован, указатель на запись блокировки в «Пометить мир» помечается как «01».
Если слово метки обновлено успешно и потоку принадлежит блокировка объекта, указатель бита блокировки выполнения помечается как «00».Если обновление не удается и слово метки текущего объекта не указывает на кадр стека текущего потока, это означает, что объект блокировки уже вытеснен другим потоком. Если за одну и ту же блокировку борются более двух потоков, облегченная блокировка больше не действует, блокировка помечается цифрой «10» и увеличивается до тяжелой блокировки.
Облегченные блокировки основаны на том факте, что большинство блокировок не конкурируют во время цикла синхронизации, поэтому это снижает потребление производительности при взаимном исключении. Конечно, если есть конкуренция блокировок, помимо взаимного исключения, можно будет избежать накладных расходов на использование мьютекса, и будут дополнительные операции для синхронного изменения бита флага.
Блокировка смещения
Смещенная блокировка предназначена для устранения всей синхронизации в отсутствие конкуренции, даже если операция обновления CAS не выполняется, она будет смещать первый поток для ее получения, если в следующем процессе выполнения блокировка не используется другим получением потока. , поток, удерживающий смещенную блокировку, никогда не будет нуждаться в синхронизации, и когда другой поток попытается получить блокировку, смещенный режим объявляется завершенным.
-----------------------------------------------------------------------------
Если вы хотите увидеть больше интересных и оригинальных технических статей, сканируйте и подписывайтесь на официальный аккаунт.
Сосредоточьтесь на личностном росте и развитии игр, а также способствуйте росту и прогрессу отечественного игрового сообщества.