Введение
На этапе проектирования разработки многопоточного приложения вы должны учитывать возможность так называемого состояния гонки, которое возникает, когда нескольким потокам необходимо изменить одно и то же. ресурс программы одновременно (одновременно). Классический пример — это когда муж и жена пытаются снять наличные в разных банкоматах одновременно.
В примере ниже у нас есть класс Account, который представляет банковский счет. Чтобы код был коротким, эта учетная запись начинается с баланса 50 и может использоваться только для вывода средств. Вывод будет принят, даже если на счете недостаточно денег для его покрытия. Учетная запись просто уменьшает баланс на сумму, которую вы хотите снять:
Java Code:
package синхронизация; открытый класс Account {private int balance = 50; public int getBalance () {return balance;} общедоступный аннулированный вывод (int amount) {balance = balance - amount;}}
Представьте себе пару, Ранджит и Риму, у которых есть доступ к аккаунту и они хотят снять средства. Но они не хотят, чтобы на счете когда-либо было овердрафт. Ниже класс AccountTesting.java запустит два потока, и оба потока будут пытаться снять деньги с одного и того же объекта учетной записи в цикле. Вывод осуществляется в два этапа:
1. Проверить баланс.
2. Если на счету достаточно (вывести10), сделайте вывод.
Код Java: перейдите в редактор
открытый класс AccountTesting реализует Runnable {private Account acct = new Account (); public static void main (String [] args) {AccountTesting r = new AccountTesting (); Thread one = new Thread (r); Thread two = new Thread (r); one.setName ("Ranjeet"); two.setName ("Reema"); one.start (); two.start ();} @ Overridepublic void run () {for (int x = 0; x = amt) {System.out.println (Thread.currentThread () .getName () + "собирается отозвать"); попробуйте {Thread.sleep (100);} catch (InterruptedException ex) {} acct.withdraw (amt); System.out.println (Thread.currentThread () .getName () + "завершает вывод");} else {System.out.println ("Недостаточно в аккаунте для" + Thread.currentThread (). getName () + "для вывода" + acct.getBalance ()) ;}}} class Account {private int balance = 50; public int getBalance () {return balance;} public void снятие (int amount) {balance = balance - amount;}}
Вывод:
Хотя каждый раз, когда вы запускаете этот код, вывод может немного отличаться, давайте рассмотрим этот конкретный пример, используя пронумерованные строки вывода. Для первых четырех попыток все нормально. Эта проблема известна как «состояние гонки», когда несколько потоков могут получить доступ к одному и тому же ресурсу (обычно к переменным экземпляра объекта) и могут создавать поврежденные данные, если один поток «участвует» слишком быстро до того, как операция, которая должна быть «атомарной», будет завершено.
Синхронизация
Синхронизация — решение этой проблемы. Специальное ключевое слово synchronized предотвращает возникновение состояний гонки. Это ключевое слово устанавливает блокировку (монитор) на важный объект или фрагмент кода, чтобы гарантировать, что только один поток будет иметь доступ одновременно.
Как вы защищаете данные? Вы должны сделать две вещи:
- Отметить переменные как частные.
- Синхронизируйте код, изменяющий переменные.
Мы можем решить все проблемы Ранджита и Римы, добавив в код одно слово. Мы отмечаем метод makeWithdrawal () как синхронизированный следующим образом:
вот модифицированный код Java
public class SynchronizedAccountTesting реализует Runnable {private Account acct = new Account (); public static void main (String [] args) {SynchronizedAccountTesting r = new SynchronizedAccountTesting (); Thread one = new Thread (r); Thread two = new Thread (r) ; one.setName ("Ranjeet"); two.setName ("Reema"); one.start (); two.start ();} @ Overridepublic void run () {for (int x = 0; x = amt) {System.out.println (Thread.currentThread (). GetName () + "собирается отозвать"); попробуйте {Thread.sleep (100);} catch (InterruptedException ex) {} acct.withdraw (amt); System .out.println (Thread.currentThread (). getName () + "завершает вывод");} else {System.out.println ("Недостаточно в аккаунте для" + Thread.currentThread (). getName () + " для вывода "+ acct.getBalance ());}}} class Account {private int balance = 50; public int getBalance () {return balance;} public void вывода (int amoun t) {balance = balance - amount;}}
Вывод:
Блокирует весь метод removewCash (), чтобы никакой другой поток не получил доступ к указанной части кода, пока текущий (блокирующий) поток не завершит выполнение функции removewCash (). Блокировки должны быть размещенными на как можно более короткое время, чтобы избежать замедления программы: вот почему синхронизация коротких блоков кода предпочтительнее, чем синхронизация целых методов.
Каждый объект в Java имеет встроенную блокировку, которая только приходит в игру, когда объект имеет код синхронизированного метода. Когда мы вводим синхронизированный нестатический метод, мы автоматически получаем блокировку, связанную с текущим экземпляром класса, код которого мы выполняем.
Резюме:
- Синхронизированные методы предотвращают одновременный доступ более чем одного потока к коду критического метода объекта.
- Вы можете использовать ключевое слово synchronized в качестве модификатора метода , или для запуска синхронизированного блока кода.
- Чтобы синхронизировать блок кода (другими словами, область действия меньше, чем весь метод), вы должны указать аргумент, который является объектом, блокировку которого вы хотите синхронизировать.
- Хотя только один поток может иметь доступ к синхронизированному коду конкретного экземпляра, несколько потоков могут по-прежнему обращаться к несинхронизированному коду одного и того же объекта.
Редактор кода Java: