Эта статья в основном последовала за двумя предыдущими статьями о многопользовании, чтобы суммировать проблемы безопасности потоков в многопользовательском языке Java.
1. Типичный пример безопасности потока Java
public Class ThreadTest {public static void main (string [] args) {account account = new Account ("123456", 1000); DrawMoneyRunnable DrawMoneyRunnable = new DrawmoneyRunnable (аккаунт, 700); Потока mythread1 = новый поток (DrawnoneyRunnable); Потока MyThread2 = новый поток (DraitMoneyRunnable); mythread1.start (); mythread2.start (); }} класс DrawMoneyRunnable реализует runnable {частная учетная запись; Частный двойной рисунок; public drawmoneyrunnable (учетная запись, двойная притяжение) {super (); this.account = account; this.drawamount = drawamount; } public void run () {if (account.getBalance ()> = draitAmount) {// 1 System.out.println («Изъятие было успешным, вывод денег -:» + drawamount); Double Balance = account.getBalance () - DrawAmount; account.setBalance (баланс); System.out.println ("Баланс:" + Баланс); }}} class account {private String accountno; частный двойной баланс; public account () {} public account (string accountno, двойной баланс) {this.accountno = accountno; this.balance = баланс; } public String getAccountno () {return accountno; } public void setAccountno (string accountno) {this.accountno = accountno; } public double getBalance () {return Balance; } public void setBalance (двойной баланс) {this.balance = balance; }}Приведенный выше пример легко понять. Есть банковская карта с балансом 1000. Программа имитирует сцену, где вы и ваша жена снимаете деньги в банкомате одновременно. Запустите эту программу несколько раз и могут иметь результаты вывода в нескольких различных комбинациях. Один из возможных выходов:
1 Вывод денег успешно, вывод денег: 700.0
2 баланс: 300,0
3 Вывод денег является успешным, вывод денег: 700.0
4 Баланс: -400.0
Другими словами, для банковской карты с балансом всего 1000 вы можете снять в общей сложности 1400, что, очевидно, является проблемой.
После анализа проблема заключается в неопределенности исполнения в многопоточной среде Java. ЦП может случайным образом переключаться между несколькими потоками в состоянии готового состояния, поэтому вполне вероятно, что возникает следующая ситуация: когда Thread1 выполняет код в // 1, условие суждения верно. В настоящее время ЦП переключается на Thread2, выполняет код AT // 1 и обнаруживает, что он все еще правда. Затем выполняется Thread2, затем переключитесь на Thread1, а затем выполнение завершено. В настоящее время появятся вышеупомянутые результаты.
Поэтому, когда речь заходит о проблемах безопасности потоков, это фактически означает, что доступ к общим ресурсам в многопоточной среде может вызвать несоответствие в этом общем ресурсе. Следовательно, чтобы избежать проблем с безопасностью потока, следует избегать одновременного доступа к этому общему ресурсу в многопоточной среде.
2. Метод синхронизации
Синхронизированная модификация ключевых слов добавляется в определение метода для доступа к общим ресурсам, создавая этот метод, называемый методом синхронизации. Можно просто понять, что этот метод заблокирован, а его заблокированный объект - сам объект, где находится текущий метод. В многопоточной среде при выполнении этого метода вы должны сначала получить эту блокировку синхронизации (и максимум может его получить только один поток). Только когда поток выполняет этот метод синхронизации, будет выпущен объект блокировки, а другие потоки могут получить эту блокировку синхронизации и т. Д.
В приведенном выше примере общий ресурс является объектом учетной записи, и при использовании метода синхронизации он может решать проблемы безопасности потоков. Просто добавьте синшронизированное ключевое слово перед методом run ().
public Synchrinized void run () {// ....}3. Синхронизировать кодовые блоки
Как проанализировано выше, для решения проблем безопасности потоков требуется только ограничение неопределенности доступа к общим ресурсам. При использовании метода синхронизации весь тело метода становится синхронным состоянием выполнения, которое может привести к возникновению диапазона синхронизации. Следовательно, другой метод синхронизации - блок кода синхронизации - может быть решен непосредственно для кода, который требует синхронизации.
Формат блока синхронного кода:
синхронизированный (obj) {// ...}Среди них OBJ является объектом блокировки, поэтому крайне важно выбрать, какой объект будет заблокирован. Вообще говоря, этот объект общего ресурса выбирается в качестве объекта блокировки.
Как и в приведенном выше примере, лучше всего использовать объект учетной записи в качестве объекта блокировки. (Конечно, это также можно выбрать, потому что в потоке создания используется метод выполнения. Если это поток, созданный, непосредственно унаследованный метод потока, используя этот объект в качестве блокировки синхронизации, на самом деле не будет играть какую -либо роль, потому что это другой объект. Поэтому вы должны быть очень осторожны при выборе блокировки синхронизации ...)
4. блокировка синхронизации объекта блокировки
Как мы можем видеть выше, именно потому, что мы должны быть настолько осторожны с выбором синхронных объектов блокировки, есть ли простое решение? Это может облегчить отделение синхронных объектов блокировки от общих ресурсов, а также хорошо решать проблемы безопасности потоков.
Использование блокировки блокировки объекта блокировки могут легко решить эту проблему. Единственное, что следует отметить, это то, что объект блокировки должен иметь отношения один к одному с объектом ресурса. Общий формат блокировки синхронизации объекта блокировки:
Класс X {// Отображение объекта, который определяет блокировку синхронизации блокировки, который имеет отношение один к одному с общим ресурсом Private Final Lock Lock = new Reentrantlock (); public void m () {// lock lock.lock (); // ... код, который требует синхронизации потока // Выпустить блокировку блокировки блокировки. Unlock (); }}5. wait ()/notify ()/notifyall () Связь потока
Эти три метода упоминаются в сообщении в блоге «Сводная серия Java: java.lang.object». Хотя эти три метода в основном используются в многопользовании, они на самом деле являются локальными методами в классе объекта. Следовательно, теоретически, любой объект объекта может использоваться в качестве основного тона этих трех методов. В фактическом многопоточном программировании только синхронизация объекта блокировки, чтобы настройка этих трех методов может быть завершена между несколькими потоками.
WAIT (): заставляет текущий поток ждать и заставьте его ввести состояние блокировки ожидания. До тех пор, пока другой поток не вызовет метод notify () или notifyall () объекта синхронного блокировки, чтобы разбудить поток.
notify (): разбудите один поток, ожидая этого синхронного объекта блокировки. Если в этом синхронном блокирующем объекте ожидается несколько потоков, один из потоков будет выбран для работы пробуждения. Только когда текущий поток отказывается от блокировки на объекте синхронного блокировки, можно выполнить пробужденный поток.
notifyall (): разбудить все потоки, ожидающие этого синхронного объекта блокировки. Только когда текущий поток отказывается от блокировки на объекте синхронного блокировки, можно выполнить пробужденный поток.
пакет com.qqyumidi; public class threadtest {public static void main (string [] args) {учетная запись = новая учетная запись ("123456", 0); Thread DrawMoneyThread = New DrawmoneyThread («Получить деньги», счет, 700); DepoTmoneyThread = New DisepitMoneyThread («Сэкономить деньги», счет, 700); drawmoneythread.start (); DepoTmoneyThread.Start (); }} класс DraitMoneyThread Extends Thread {Private Account Account; частная двойная сумма; public DrawmoneyThread (String ThreadName, учетная запись, двойная сумма) {Super (ThreadName); this.account = account; this.amount = сумма; } public void run () {for (int i = 0; i <100; i ++) {account.draw (сумма, i); }}} класс DepositeMoneyThread Extends Thread {Private Account Account; частная двойная сумма; public disepitmoneythread (String Threadname, учетная запись, двойная сумма) {super (threadname); this.account = account; this.amount = сумма; } public void run () {for (int i = 0; i <100; i ++) {account.deposit (сумма, i); }}} class account {private String accountno; частный двойной баланс; // Определите, есть ли уже депозит на счете частного логического флага = false; public account () {} public account (string accountno, двойной баланс) {this.accountno = accountno; this.balance = баланс; } public String getAccountno () {return accountno; } public void setAccountno (string accountno) {this.accountno = accountno; } public double getBalance () {return Balance; } public void setBalance (двойной баланс) {this.balance = balance; } / ** * Сэкономьте деньги * * @param depositamount * / public synchronized void -депозит (Double Depositamount, int i) {if (flag) {// Кто -то в учетной записи уже сохранил деньги, а текущий поток должен ждать, чтобы заблокировать try {System.out.println (thread.currentThread (). GetName () + »Начала, чтобы выполнить aTempute aTempute aepte aepture ждать(); // 1 System.out.println (thread.currentThread (). GetName () + «выполнение операции ожидания» + » - i =" + i); } catch (прерванное искусство e) {e.printstacktrace (); }} else {// start saving system.out.println (thread.currentthread (). getName () + "Depoit:" + depositAmount + " - i =" + i); SetBalance (баланс + депонист); flag = true; // Просыпать другие потоки notifyall (); // 2 try {thread.sleep (3000); } catch (прерванное искусство e) {e.printstacktrace (); } System.out.println (think.currentThread (). GetName () + "- сэкономить деньги- выполнение завершено" + "- i =" + i); }} / ** * Снять деньги * * @param drawamount * / public synchronized void draw (double drawamount, int i) {if (! Flag) {// Никто в учетной записи еще не сохранил деньги, и текущий поток должен ждать, чтобы заблокировать try {system.out.println (Thread.currentThread () i = " + i); ждать(); System.out.println (thread.currentThread (). GetName () + "Выполнить операцию ожидания" + "Выполнить операцию ожидания" + " - i =" + i); } catch (прерванное искусство e) {e.printstacktrace (); }} else {// Начать снятие денег System.out.println (thread.currentThread (). getName () + "Снятие денег:" + draitAmount + " - i =" + i); SetBalance (getBalance () - DrawAmount); flag = false; // Просыпать другие потоки notifyall (); System.out.println (think.currentThread (). GetName () + "-снять деньги-выполнение завершено" + "-i =" + i); // 3}}} Приведенный выше пример демонстрирует использование wait ()/notify ()/notifyall (). Некоторые выходные результаты:
Поток снятия денег начинает выполнять операцию ожидания и выполнять операцию ожидания ... i = 0
Защита сбережений потока: 700.0 - i = 0
Сэкономьте денежную тему с заседанием денег-я = 0
Поток для экономии денег должен выполнить операцию ожидания ... i = 1
Поток снятия денег выполняет операцию ожидания и операцию ожидания ... i = 0
Снять деньги с выводом денег: 700.0 - i = 1
Поток денег-Withdrawal-Execution-I = 1
Поток для снятия денег должен начать выполнять операцию ожидания и выполнить операцию ожидания ... i = 2
Поток с экономией денег выполняет операцию ожидания ... i = 1
Защита сэкономить залог потока: 700.0 - i = 2
Сэкономьте денежную тему с заседанием денег-я = 2
Поток снятия выполняет операцию ожидания и выполняет операцию ожидания- i = 2
Снять денежную тему сняла деньги: 700.0 - i = 3
Поиск денег-Withdrawal-Execution-i = 3
Поток для снятия денег должен выполнить операцию ожидания и выполнить операцию ожидания ... i = 4
Защита сэкономить нить залог: 700.0 - i = 3
Сэкономьте денежную тему с заседанием денег-я = 3
Поток для экономии денег должен выполнить операцию ожидания ... i = 4
Поток снятия денег выполняет операцию ожидания и операцию ожидания ... i = 4
Снять денежную тему сняла деньги: 700.0 - i = 5
Поток средств по снятию средств
Поток для снятия денег должен начать выполнять операцию ожидания и выполнять операцию ожидания ... i = 6
Поток с экономией денег выполняет операцию ожидания ... i = 4
Защита сбережений потока: 700.0 - i = 5
Сэкономьте денежную тему с заседанием денег-я = 5
Поток для экономии денег должен выполнить операцию ожидания ... i = 6
Поток снятия денег выполняет операцию ожидания и операцию ожидания ... i = 6
Снять денежную тему сняла деньги: 700.0 - i = 7
Поток средств по снятию средств-Withdrawal-Execution-i = 7
Поток снятия денег начинает выполнять операцию ожидания и выполнять операцию ожидания-я = 8
Поток экономии денег выполняет операцию ожидания ... i = 6
Защита сэкономить нить залог: 700.0 - i = 7
Поэтому нам нужно обратить внимание на следующие моменты:
1. После выполнения метода wait () текущий поток немедленно входит в состояние блокировки ожидания, и последующий код не будет выполнен;
2. После выполнения метода notify ()/notifyall (), объект потока (any-notify ()/all-notifyall ()) на этом объекте блокировки синхронизации будет пробужден. Однако в настоящее время объект блокировки синхронизации не выпускается. То есть, если есть код, стоящий за notify ()/notifyall (), он будет продолжать продолжаться. Только когда текущий поток будет выполнен, будет выпущен объект блокировки синхронизации;
3. После notify ()/notifyall () выполняется, если справа существует метод Sleep (), текущий поток будет входить в состояние блокировки, но блокировка объекта синхронизации не высвобождается и все еще сохраняется сама по себе. Затем поток будет продолжать выполняться через определенный период времени, следующие 2;
4. wate ()/notify ()/nitifyall (). Завершает связь или сотрудничество между потоками на основе различных блоков объектов. Поэтому, если это другая блокировка объекта синхронизации, он потеряет свое значение. В то же время блокировка объекта синхронизации лучше всего поддерживать соответствие одно-одному с общим объектом ресурса;
5. Когда поток ожидания просыпается и выполняется, код метода wait (), который был выполнен в прошлый раз, продолжает выполняться.
Конечно, приведенный выше пример относительно прост, просто для простого использования метода wait ()/notify ()/noitifyall (), но, по сути, это уже простая модель производителя-потребителя.
Серия статей:
Объяснение многопоточных экземпляров Java (i)
Подробное объяснение многопоточных экземпляров Java (II)
Подробное объяснение многопоточных экземпляров Java (III)