краткое содержание
Эта серия основана на ходе переработки чисел в золото, и для того, чтобы лучше учиться, была сделана серия записей. Эта статья в основном представляет: 1. Идеи и методы оптимизации блокировки 2. Оптимизация блокировки в виртуальной машине 3. Случай неправильного использования блокировки 4. Threadlocal и его анализ исходного кода
1. Идеи и методы оптимизации блокировки
Уровень параллелизма упоминается во введении в [высокую параллельную Java 1].
Как только блокировка используется, это означает, что это блокирует, поэтому параллелизм, как правило, немного ниже, чем ситуация без блокировки.
Оптимизация блокировки, упомянутая здесь, относится к тому, как предотвратить работу производительности в случае блокировки. Но независимо от того, как вы его оптимизируете, производительность, как правило, будет немного хуже, чем ситуация без блокировки.
Здесь следует отметить, что Trylock в Reentrantlock, упомянутый в [высокий параллельный пакет Java v] JDK, пакет 1, как правило, является методом без блокировки, поскольку он не будет висеть, когда судьи Trylock.
Подводя итог идей и методы оптимизации блокировки, существуют следующие типы.
1.1 Уменьшите время удержания блокировки
public synchrinized void syncmethod () {owercode1 (); mutextmethod (); Другое chode2 (); } Как и приведенный выше код, вам нужно получить блокировку перед входом в метод, а другие потоки должны ждать снаружи.
Точка оптимизации здесь состоит в том, чтобы сократить время ожидания других потоков, поэтому она используется только для добавления замков в программы с требованиями безопасности потока.
public void syncmethod () {othercode1 (); синхронизированный (это) {mutextmethod (); } othercode2 (); }1.2 Уменьшите размер блокировки частиц
Разделение больших объектов (этот объект может быть доступен многими потоками) на небольшие объекты, значительно увеличивая параллелизм и снижая конкуренцию за блокировки. Только за счет сокращения конкуренции за замки и смещения в сторону замков, уровень успеха легких замков будет улучшен.
Наиболее типичным случаем уменьшения гранулярности замка является совместный шахта. Это упоминается в [высокой параллелистике Java v] JDK -пакета параллелизма 1.
1.3 разделение блокировки
Наиболее распространенным разделением блокировки является чтение считывания блокировки readwriteLock, который разделен на блокировки чтения и записи в соответствии с функцией. Таким образом, чтение и чтение не являются взаимоисключающими, чтение и написание являются взаимоисключающими, что обеспечивает безопасность нити и повышает производительность. Для получения подробной информации, пожалуйста, проверьте [High Complornuence Java V] JDK -пакет параллелизма 1.
Идея разделения чтения и письма может быть расширена, и до тех пор, пока операции не влияют друг на друга, замок может быть разделен.
Например, LinkedBlockingQueue
Выберите его с головы и поместите данные с хвоста. Конечно, это также аналогично краже работы в Forkjoinpool, упомянутом в [High Complornuence Java VI] JDK -пакет 2.
1.4 Заблокировать шероховатое
Вообще говоря, чтобы обеспечить эффективную параллелизм между несколькими потоками, каждый поток потребуется для того, чтобы держать замок как можно более короткий, то есть замок должен быть выпущен сразу после использования общественных ресурсов. Только таким образом другие потоки, ожидающие этого блокировки, получить ресурсы для выполнения задач как можно скорее. Тем не менее, все имеет степень. Если тот же замок постоянно запрашивается, синхронизируется и выпускается, он будет потреблять ценные ресурсы системы, что не способствует оптимизации производительности.
Например:
public void demomethod () {synchronized (lock) {// do Sth. } // Делать другую нежелательную синхронизационную работу, но может быть выполнена быстро синхронизировать (lock) {// do Sth. }} В этом случае, в соответствии с идеей шероховатости блокировки, она должна быть объединена
public void demomethod () {// Интеграция в синхронизированный запрос на блокировку (Lock) {// do Sth. // выполнять другую нежелательную синхронизационную работу, но она может быть выполнена быстро}}}} Конечно, существует предпосылка, работа в середине, которая не требует синхронизации, будет выполнена быстро.
Позвольте мне привести еще один крайний пример:
for (int i = 0; i <circle; i ++) {synchronized (lock) {}} Замки должны быть получены в петле. Хотя JDK оптимизирует этот код внутри.
Synchronized (lock) {for (int i = 0; i <circle; i ++) {}} Конечно, если есть необходимость сказать, что такой цикл слишком длинный, и вам нужно дать другие потоки, чтобы не ждать слишком долго, то вы можете написать его только как выше. Если нет таких похожих требований, лучше написать его непосредственно в следующую.
1,5 блокировка устранения
Устранение блокировки - это вещь на уровне компилятора.
В мгновенном компиляторе, если обнаружены объекты, которые невозможно поделиться, можно устранить работу блокировки этих объектов.
Может быть, вы найдете странным, что, поскольку некоторые объекты не могут быть доступны по нескольким потокам, почему я должен добавлять блокировки? Разве не было бы лучше просто не добавлять блокировки при написании кода.
Но иногда эти замки не написаны программистами. Некоторые из них имеют замки в реализациях JDK, такие как такие классы, как Vector и StringBuffer. Многие из их методов имеют замки. Когда мы используем методы этих классов без безопасности потока, когда будут выполнены определенные условия, компилятор удалит блокировку для повышения производительности.
например:
public static void main (string args []) бросает прерывание {long start = system.currenttimemillis (); for (int i = 0; i <2000000; i ++) {reafestringBuffer ("JVM", "Диагностика"); } long buffercost = system.currenttimemilsis () - start; System.out.println ("craetestringbuffer:" + buffercost + "ms"); } public Static String createstringBuffer (String S1, String S2) {stringBuffer sb = new StringBuffer (); SB.Append (S1); SB.Append (S2); вернуть sb.toString (); } Stringbuffer.append в приведенном выше коде является синхронной операцией, но StringBuffer является локальной переменной, и метод не возвращает StringBuffer, поэтому для доступа к ней невозможно получить доступ
Тогда операция синхронизации в StringBuffer в настоящее время бессмысленна.
Отмена блокировки установлена на параметрах JVM, конечно, она должна быть в режиме сервера:
-server -xx:+Doscapeanalysis -xx:+elmiNateLocks
И включите анализ побега. Функция анализа побега состоит в том, чтобы увидеть, может ли переменная, вероятно, уйдет от области.
Например, в приведенном выше StringBuffer возвращение CraetestringBuffer в приведенном выше коде является строкой, поэтому эта локальная переменная StringBuffer не будет использоваться в другом месте. Если вы измените CraetestringBuffer на
Public Static StringBuffer CraetestringBuffer (String S1, String S2) {StringBuffer sb = new StringBuffer (); SB.Append (S1); SB.Append (S2); вернуть SB; } Затем после возвращения этого StringBuffer он может использоваться где угодно (например, основная функция вернет результат и поместит его в карту и т. Д.). Затем можно проанализировать анализ Escape JVM, что эта локальная переменная stringbuffer избегает своей области.
Поэтому, основываясь на анализе побега, JVM может судить, что если локальная переменная stringbuffer не избегает своей области, можно определить, что к StringBuffer не будет доступ к нескольким потокам, а затем эти дополнительные блокировки могут быть удалены для повышения производительности.
Когда параметры JVM:
-server -xx:+Doscapeanalysis -xx:+elmiNateLocks
Выход:
Craetestringbuffer: 302 мс
Параметры JVM:
-server -xx:+Doscapeanalysis -xx: -eliminatelocks
Выход:
CraetestringBuffer: 660 мс
Очевидно, что эффект устранения блокировки все еще очень очевиден.
2. Оптимизация блокировки в виртуальной машине
Во -первых, нам нужно представить заголовок объекта. В JVM каждый объект имеет заголовок объекта.
Mark Word, маркер для заголовка объекта, 32-битная (32-битная система).
Опишите хэш, информация о блокировке, теги сбора мусора, возраст
Это также сохранит указатель на запись блокировки, указатель на монитор, идентификатор сбоя блокировки и т. Д.
Проще говоря, заголовок объекта - сохранить некоторую систематическую информацию.
2.1 положительный замок
Так называемым смещением является эксцентриситет, то есть замок будет стремиться к потоке, которая в настоящее время владеет блокировкой.
В большинстве случаев нет конкуренции (в большинстве случаев блок синхронизации не имеет нескольких потоков одновременно в блокировке конкуренции), поэтому производительность может быть улучшена путем смещения. То есть, когда нет конкуренции, когда поток, который ранее получен, блокировка снова получает блокировку, он определит, указывает ли блокировка мне, поэтому потоке не нужно будет снова получить блокировку и может непосредственно ввести блок синхронизации.
Реализация блокировки смещения состоит в том, чтобы установить знак отметки заголовка объекта как смещенная и записать идентификатор потока на отметку заголовка объекта.
Когда другие потоки запрашивают ту же блокировку, режим смещения заканчивается
JVM позволяет блокировать смещение по умолчанию -xx:+usebiasedlocking
В жесткой конкуренции, предвзятая блокировка увеличит бремя системы (суждение о том, предвзято ли он добавляется каждый раз)
Пример предвзятого блокировки:
Пакет Test; Import java.util.list; import java.util.vector; Общедоступный тест класса {public Statics <Integer> numberlist = new Vector <Integer> (); public static void main (string [] args) бросает прерывание {long begin = system.currenttimemillis (); int count = 0; int startnum = 0; while (count <10000000) {numberlist.add (startNum); Startnum += 2; count ++; } long End = System.currentTimeMillis (); System.out.println (End - Begin); }} Vector-это безопасный класс, который использует механизм блокировки внутри. Каждый раз добавить, будет сделан запрос на блокировку. Приведенный выше код имеет только один поток основной, а затем многократно добавляет запрос на блокировку.
Используйте следующие параметры JVM, чтобы установить блокировку смещения:
-Xx:+usebiasedlocking -xx: biasedlockingstartupdelay = 0
BiasedLockingStartupDelay означает, что блокировка смещения включена после того, как система запускается в течение нескольких секунд. По умолчанию 4 секунды, потому что, когда система запускается, общая конкуренция данных относительно жесткая. В настоящее время включенные блокировки смещения снизит производительность.
Так как здесь, чтобы проверить производительность блокировки смещения, время блокировки смещения задержки установлено на 0.
В настоящее время вывод 9209
Выключите блокировку смещения ниже:
-Xx: -себиаседлокинг
Выход 9627
Как правило, когда нет конкуренции, производительность включения блокировки смещения будет улучшена примерно на 5%.
2.2 Легкий замок
Многопоточная безопасность Java реализуется на основе механизма блокировки, а производительность блокировки часто не является удовлетворительной.
Причина в том, что Monitorenter и Monitorexit, два примитива Bytecode, которые управляют синхронизацией многопоточной, реализуются JVM, полагаемыми на Mutex для операционной системы.
Mutex-это относительно ресурсная операция, которая заставляет поток висеть и необходимо перенести обратно в исходный поток за короткий промежуток времени.
Чтобы оптимизировать механизм блокировки Java, концепция легкого замка была введена с Java6.
Легкая блокировка предназначена для снижения вероятности многопоточного входа в Mutex, а не заменить Mutex.
Он использует примитивную CPU Compare-and Swap (CAS, инструкция по сборке CMPXCHG) и пытается исправить, прежде чем войти в Mutex.
Если блокировка BIAS не удастся, система выполнит легкую операцию блокировки. Цель его существования состоит в том, чтобы избежать использования мутекса на уровне операционной системы, насколько это возможно, потому что эта производительность будет относительно плохой. Поскольку сам JVM является приложением, я надеюсь решить проблему синхронизации потока на уровне приложения.
Подводя итог, легкий замок является методом быстрой блокировки. Перед входом в Mutex используйте операции CAS, чтобы попытаться добавить замки. Постарайтесь не использовать Mutex на уровне операционной системы для повышения производительности.
Затем, когда замок смещения выходит из строя, шаги легкого блокировки:
1. Сохраните указатель отметки заголовка объекта в заблокированный объект (объект здесь относится к заблокированному объекту, такому как синхронизированный (это) {}, это объект здесь).
lock-> set_displed_header (mark);
2. Установите заголовок объекта в качестве указателя на блокировку (в пространстве стека потоков).
if (mark == (markoop) atomic :: cmpxchg_ptr (lock, obj ()-> mark_addr (), mark)) {tevent (slow_enter: релиз стеклок); возвращаться ; } Замок расположен в стеке резьбы. Следовательно, чтобы определить, удерживает ли поток этот блокировку, просто определите, находится ли пространство, на которое указано заголовок объекта в адресном пространстве стека потока.
Если легкий замок не стерж, это означает, что существует конкуренция и обновление до тяжелого веселя (обычный блокировка), который является методом синхронизации на уровне операционной системы. В отсутствие конкуренции с блокировками легкие замки снижают потерю производительности, вызванную традиционными замками с использованием OS Mutexes. Когда конкуренция очень жесткая (легкие замки всегда терпят неудачу), легкие замки делают много дополнительных операций, что приводит к снижению производительности.
2.3 Спиновая замка
Когда существует конкуренция, поскольку легкая попытка блокировки терпит неудачу, она может быть непосредственно обновлена до тяжелого веса для использования взаимного исключения на уровне операционной системы. Также можно снова попробовать спин -блокировку.
Если поток может быстро получить блокировку, то вы не можете повесить резьбу на слое ОС, позвольте потоке сделать несколько пустых операций (SPIN) и постоянно попытаться получить блокировку (аналогично TryLock). Конечно, количество петель ограничено. Когда количество петли достигнет, оно все равно будет обновлено до тяжелого весе. Поэтому, когда у каждой потока мало времени, чтобы удерживать блокировку, Spin Lock может попытаться избежать подвесных потоков на слое ОС.
JDK1.6 -xx:+USESPINNing включено
В JDK1.7 удалите этот параметр и измените его на встроенную реализацию.
Если блок синхронизации очень длинный, а спин не сбои, производительность системы будет ухудшена. Если блок синхронизации очень короткий, а спин успешно, он экономит время переключения подвески потока и улучшает производительность системы.
2.4 положительный блокировка, легкий замок, резюме спинового блокировки
Приведенный выше блокировка не является методом оптимизации блокировки на уровне Java, но встроен в JVM.
Прежде всего, смешивание замков состоит в том, чтобы избежать потребления производительности потока, когда он неоднократно снимает/выпускает тот же замок. Если тот же поток по -прежнему приобретает эту блокировку, он напрямую вводит блок синхронизации при попытке сметить блокировки, и нет необходимости снова получить блокировку.
Легкие блокировки и спиновые замки предназначены для того, чтобы избежать прямых вызовов в операции Mutex на уровне операционной системы, поскольку приостановка потоков является очень операцией, обслуживающей ресурсы.
Чтобы избежать использования замков в тяжелом весе (Mutex на уровне операционной системы), мы сначала попробуем легкий замок. Легкий замок попытается использовать работу CAS для получения блокировки. Если легкий замок не может получить, это означает, что есть конкуренция. Но, может быть, вы скоро получите замок, и вы попробуете вращать замки, сделаете несколько пустых петель на потоке и попытаетесь получать замки каждый раз, когда вы циклете. Если замок спина также не удается, он может быть обновлен только до тяжелого веса.
Видно, что смещенные замки, легкие замки и спиновые замки - все оптимистичные замки.
3. Случай неправильного использования замков
открытый класс Integerlock {статический целое число I = 0; public Static Class AddThread Extends Thread {public void run () {for (int k = 0; k <100000; k ++) {synchronized (i) {i ++; }}}} public static void main (string [] args) бросает прерывание {addThread t1 = new addThread (); AddThread T2 = new AddThread (); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} Очень основная ошибка заключается в том, что в дизайне конструкции параллелизма [высокого параллелизма Java VII] интернет -интернет является окончательным неизменным, и после каждого ++ будет генерироваться и назначено новое интернет, поэтому замки соревнуются между двумя потоками. Так что это не безопасно.
4. Threadlocal и его анализ исходного кода
Это может быть немного неуместно упомянуть здесь Threadlocal, но Threadlocal - это способ заменить блокировки. Так что все еще необходимо упомянуть об этом.
Основная идея состоит в том, что в многопоточном, конфликту данных необходимо заблокировать. Если используется Threadlocal, для каждого потока предоставляется экземпляр объекта. Различные потоки получают доступ только к своим собственным объектам, а не к другим объектам. Таким образом, нет необходимости существовать замок.
Тест пакета; импорт java.text.parseexception; import java.text.simpledateformat; import java.util.date; import java.util.concurrent.executorservice; import java.util.concurrent.executors; public class {private static umpredateformat sdf = new Simpredateformators; HH: MM: SS "); Public Static Class Parsedate реализует Runnable {int i = 0; Public Pacedate (int i) {this.i = i; } public void run () {try {date t = sdf.parse ("2016-02-16 17:00:" + i % 60); System.out.println (i + ":" + t); } catch (parseexception e) {e.printstacktrace (); }}} public static void main (string [] args) {rececusterService es = executors.newfixedthreadpool (10); for (int i = 0; i <1000; i ++) {es.execute (new Parsedate (i)); }}} Поскольку SimpleDateFormat не является защищенным потоком, приведенный выше код используется неправильно. Самый простой способ - это определить класс самостоятельно и обернуть его синхронизированным (аналогично Collections.synchronizedMap). Это вызовет проблемы при выполнении этого в высокой параллелистике. Конфликт на синхронизированных приводит только к тому, что одна нить входит за раз, а объем параллелизма очень низкий.
Эта проблема решается с использованием Threadlocal для инкапсуляции SimpleDateFormat.
Тест пакета; импорт java.text.parseexception; import java.text.simpledateformat; импорт java.util.date; import java.util.concurrent.executorservice; import java.util.concurrent.executors; public class test {static thinklocal <simpledateformat> tl wallocloclocalors; Public Static Class Parsedate реализует Runnable {int i = 0; Public Pacedate (int i) {this.i = i; } public void run () {try {if (tl.get () == null) {tl.set (new SimpleDateFormat ("yyyy-mm-dd hh: mm: ss")); } Дата t = tl.get (). Parse ("2016-02-16 17:00:" + i % 60); System.out.println (i + ":" + t); } catch (parseexception e) {e.printstacktrace (); }}} public static void main (string [] args) {rececusterService es = executors.newfixedthreadpool (10); for (int i = 0; i <1000; i ++) {es.execute (new Parsedate (i)); }}}Когда каждый поток запускается, он определит, имеет ли текущий поток объект SimpleDateFormat.
if (tl.get () == null)
Если нет, то New SimpleDateFormat будет связан с текущим потоком
tl.set (new SimpleDateFormat ("yyyy-mm-dd HH: MM: SS"));
Затем используйте SimpleDateFormat текущего потока для анализа
tl.get (). Parse ("2016-02-16 17:00:" + i % 60);
В начальном коде была только один SimpleDateFormat, который использовал Threadlocal, и для каждого потока был новый SimpleDateFormat.
Следует отметить, что вы не должны устанавливать общественность SimpleDateFormat для каждого потока здесь, так как это бесполезно. Каждому нужно дать новое в промедлатеформит.
В Hibernate существуют типичные приложения для Threadlocal.
Давайте посмотрим на реализацию исходного кода Threadlocal
Прежде всего, в классе потока есть переменная -член:
Threadlocal.Threadlocalmap Threadlocals = null;
И эта карта является ключом к реализации Threadlocal
public void set (t value) {Thread t = Thread.currentThread (); Threadlocalmap map = getMap (t); if (map! = null) map.set (это, значение); иначе CreateMap (t, значение); } Согласно Threadlocal, вы можете установить и получить соответствующее значение.
Реализация Threadlocalmap здесь похожа на HashMap, но есть различия в обработке хэш -конфликтов.
Когда в Threadlocalmap возникает хэш -конфликт, не похоже на то, что HashMap для использования связанных списков для разрешения конфликта, но для размещения индекса ++ в следующем индексе для разрешения конфликта.