Давайте впервые прочитаем подробное объяснение синхронизации:
Синхронизированный является ключевым словом на языке Java. Когда он используется для изменения метода или кодового блока, он может убедиться, что в максимум максимум один поток выполняет код одновременно.
1. Когда два параллельных потока получают доступ к этому синхронизированному (этому) синхронизированному кодовому блоку в одном объекте объекта, в течение одного времени можно выполнить только один поток. Другой поток должен ждать, пока текущий поток выполнит этот кодовый блок, прежде чем он сможет выполнить кодовый блок.
2. Однако, когда один поток обращается к синхронизированному (этой) кодовому блоку синхронизации объекта, другой поток все еще может получить доступ к несинхронизированному (это) блоку кода синхронизации в этом объекте.
3. Особенно важно, чтобы, когда поток обращается к синхронизированному (этой) блоке кода синхронизации объекта, другие потоки будут заблокированы от доступа к всем другим блокам кода синхронизации синхронизации (это) в объекте.
4. Третий пример также относится к другим блокам синхронного кода. То есть, когда поток обращается к синхронизированному (этой) кодовому блоку синхронизации объекта, он получает блокировку объекта этого объекта. В результате другие потоки доступа ко всем частям синхронного кода объекта объекта временно заблокированы.
5. Приведенные выше правила также применяются к другим блокировкам объектов.
Проще говоря, синхронизированный объявляет блокировку для текущего потока. Поток с этой блокировкой может выполнять инструкции в блоке, а другие потоки могут ждать только замок, чтобы приобрести его до той же работы.
Это очень полезно, но я столкнулся с другой странной ситуацией.
1. В одном и том же классе есть два метода: использование объявления ключевого слова синхронизированное
2. При выполнении одного из методов вам нужно ждать выполнения другого метода (асинхронного обратного вызова потока), поэтому вы используете Countdownlatch для ожидания.
3. Код деконструирован следующим образом:
синхронизированный void a () {countdownlatch = new countdownlatch (1); // Do Some Counting countdownlatch.await ();} синхронизированный void b () {countdownlatch.countdown ();} в
Метод A выполняется основным потоком, метод B выполняется асинхронным потоком, а результат выполнения обратного вызова - это:
Основной поток начинает застрять после выполнения метода А, и больше не это делает, и для вас будет бесполезно ждать, как бы долго он займет.
Это классическая проблема тупика
Ожидается, когда B выполнит, но на самом деле не думайте, что B - обратный вызов, B также ждет выполнения. Почему? Синхронизированный играет роль.
Вообще говоря, когда мы хотим синхронизировать блок кода, нам нужно использовать общую переменную, например, для ее блокировки:
byte [] mutex = new Byte [0]; void a1 () {synchronized (mutex) {// dosomething}} void b1 () {synchronized (mutex) {// dosomething}} Если содержимое метода A и B -метода перенесено в синхронизированные блоки методов A1 и B1 соответственно, его будет легко понять.
После того, как A1 будет выполнен, он будет косвенно ждать выполнения метода (Countdownlatch) B1.
Однако, поскольку мутекс в A1 не выпущен, мы начинаем ждать B1. В настоящее время, даже если метод асинхронного обратного вызова B1 должен ждать, пока Mutex выпустит блокировку, метод B не будет выполнен.
Это вызвало тупик!
Синхронизированное ключевое слово здесь расположено перед методом, и функция одинакова. Просто язык Java помогает вам скрыть декларацию и использование Mutex. Синхронизированный метод, используемый в одном и том же объекте, одинаков, поэтому даже асинхронный обратный вызов вызовет тупики, поэтому обратите внимание на эту проблему. Этот уровень ошибки заключается в том, что синхронизированное ключевое слово используется неправильно. Не используйте его случайным образом и используйте правильно.
Так что же такое невидимый объект Mutex?
Сам пример легко придумать. Потому что таким образом, нет необходимости определять новый объект и делать замок. Чтобы доказать эту идею, вы можете написать программу, чтобы доказать это.
Идея очень проста. Определите класс, и есть два метода. Один объявляется синхронизирован, а другой используется синхронизирован (это) в организме метода. Затем запустите два потока, чтобы назвать эти два метода отдельно. Если конкуренция за блокировки происходит между двумя методами (ожиданием), можно объяснить, что невидимый мутекс синхронизированный, объявленный методом, фактически является самим экземпляром.
открытый класс MultiThreadSync {public Synchronized void M1 () бросает прерывание {Система. out.println ("M1 Call"); Нить. Сон (2000); Система. out.println ("M1 Call Dode"); } public void m2 () Throws прерванного эктриэкцепции {synchronized (this) {System. out.println ("M2 Call"); Нить. Сон (2000); Система. out.println ("M2 Call Dode"); }} public static void main (string [] args) {final multiThreadSync thisObj = new MultiThreadSync (); Поток t1 = new Thread () {@Override public void run () {try {thisObj.m1 (); } catch (прерванное искусство e) {e.printstacktrace (); }}}}; Thread T2 = new Thread () {@Override public void run () {try {thisObj.m2 (); } catch (прерванное искусство e) {e.printstacktrace (); }}}; t1.start (); t2.start (); }} Результат вывод:
M1 Callm1 Call Donem2 Callm2 Call Donid
Объясняется, что блок синхронизации метода M2 ждет выполнения M1. Это может подтвердить вышеуказанную концепцию.
Следует отметить, что когда синхронизация добавляется в статический метод, поскольку это метод уровня класса, заблокированный объект является экземпляром класса текущего класса. Вы также можете написать программу, чтобы доказать это. Здесь он опущен.
Следовательно, синхронизированное ключевое слово метода может быть автоматически заменено синхронизированным (это) {} при его чтении, что легко понять.
void method () {void synchronized method () {synchronized (this) {// biz code // biz code} ------ >>>}} Видимость памяти из синхронизированной
В Java все мы знаем, что ключевое слово синхронизируется для реализации взаимного исключения между потоками, но мы часто забываем, что у него есть другая функция, то есть для обеспечения видимости переменных в памяти, то есть, когда два потока читают и записывают доступ к одной и той же переменной в одно и то же время, синхронизированный используется, чтобы гарантировать, что потоковая тема обновляет переменные, и прочитанные потоки могут считываться.
Например, следующий пример:
Общедоступный класс Nevisibility {Private Static Boolean Ready = false; Частный статический int № = 0; Private Static Class ReaderThread Extends Thread {@Override public void run () {while (! Готово) {thread.yield (); // Поддержка ЦП, чтобы позволить другим потокам работать} System.out.println (number); }} public static void main (string [] args) {new ReaderThread (). start (); число = 42; Готово = правда; }}Как вы думаете, что будет выводить чтение потоков? 42? При нормальных обстоятельствах 42 будет выходить. Однако из -за переупорядочивания поток чтения может выводить 0 или выводить ничего.
Мы знаем, что компилятор может изменить код при составлении кода Java в Bytecode, а ЦП может также переупорядочить свои инструкции при выполнении инструкций машины. Пока переупорядочение не разрушает семантику программы-
В одном потоке, до тех пор, пока переупорядочение не влияет на результат выполнения программы, нельзя гарантировать, что операции в нем должны выполняться в порядке, указанном в программе, даже если переупорядочение может оказать существенное влияние на другие потоки.
Это означает, что выполнение оператора «ready = true» может иметь приоритет над выполнением оператора «number = 42». В этом случае поток чтения может вывести значение по умолчанию номера 0.
В рамках модели памяти Java проблемы переупорядочения приведут к таким проблемам видимости памяти. В рамках модели памяти Java каждый поток имеет свою собственную рабочую память (в основном кеш или регистр процессора), а его операции по переменным выполняются в собственной рабочей памяти, в то время как связь между потоками достигается за счет синхронизации между основной памятью и рабочей памятью потока.
Например, для приведенного выше примера поток записи успешно обновленных номеров до 42 и готова к TRUE, но вполне вероятно, что поток записи только синхронизирует число с основной памятью (возможно, из -за буфера записи ЦП), что приводит к тому, что готовое значение, считываемое по последующим потокам чтения, всегда является ложным, поэтому вышеуказанный код не выводит каких -либо численных значений.
Если мы используем синхронизированное ключевое слово для синхронизации, такой проблемы не будет.
Общедоступный класс Nevisibility {Private Static Boolean Ready = false; Частный статический int № = 0; частный статический объект Lock = new Object (); Private Static Class ReaderThread Extends Thread {@Override public void run () {synchronized (lock) {while (! ready) {thread.yield (); } System.out.println (номер); }} public static void main (string [] args) {synchronized (lock) {new ReaderThread (). start (); число = 42; Готово = правда; }}} Это связано с тем, что модель памяти Java предоставляет следующие гарантии для синхронизированной семантики.
То есть, когда Threada выпускает Lock M, переменные, которые он написал (например, X и Y, которые присутствуют в ее рабочей памяти), будут синхронизированы с основной памятью. Когда ThreadB применяется к той же блокировке M, рабочая память ThreatB будет установлена на недействительную, а затем ThintB будет перезагрузить переменную, к которой он хочет получить доступ из основной памяти в свою рабочую память (в настоящее время x = 1, y = 1, является последним значением, измененным в Threada). Таким образом, связь между потоками от Threada к Threadb достигается.
На самом деле это одно из правил, которые определили JSR133. JSR133 определяет следующий набор случайных правил для модели памяти Java.
Фактически, этот набор правил, прежде всего, определяет видимость памяти между операциями. Если работает, что происходит до работы B, то результат выполнения операции (например, написание в переменные) должен быть виден при выполнении операции B.
Чтобы получить более глубокое понимание этих правил, давайте возьмем пример:
// код, общий по потоке A и B объекта Lock = new Object (); int a = 0; int b = 0; int c = 0; // Поток a, вызовите следующий код синхронизированный (lock) {a = 1; // 1 b = 2; // 2} // 3c = 3; // 4 // Поток B, вызовите следующий код синхронизированный (lock) {// 5 System.out.println (a); // 6 System.out.println (b); // 7 System.out.println (c); // 8}Мы предполагаем, что поток A выполняется сначала, присваивает значения трем переменным A, B и C соответственно (примечание: назначение переменных A, B выполняется в блоке синхронных операторов), а затем снова работает поток B, читая значения этих трех переменных и распечатывая их. Итак, каковы значения переменных a, b и c распечатаны потоком B?
В соответствии с правилом однопоточного, в выполнении потока A мы можем получить, что 1 операция происходит до 2 операций, 2 операции произойдут до 3 операций, а 3 операция произойдет до 4 операций. Аналогичным образом, при выполнении потока B операции 5 происходят до 6 операций, 6 операций происходят до 7 операций, а 7 операций происходят до 8 операций. В соответствии с принципами разблокировки и блокировки монитора, 3 операции (операция разблокировки) происходит до 5 операций (операция блокировки). В соответствии с переходными правилами, мы можем сделать вывод, что операции 1 и 2 происходят до операций 6, 7 и 8.
Согласно семантике памяти, в результате чего результаты выполнения операций 1 и 2 видны для операций 6, 7 и 8, поэтому в потоке B и B напечатаны 1 и 2. Для операций 4 и операции 8 переменной c. Мы не можем сделать вывод операции 4 произойдет до операции 8 в соответствии с существующими случаями до правил. Следовательно, в потоке B переменная, доступная к C, все еще может быть 0, а не 3.