text: Синхронизирано с Java: Какво представлява синхронизация на нишки в Java - Други
  java synchronized what is thread synchronization java
  Този урок обяснява синхронизацията на нишките в Java заедно със свързани понятия като Java Lock, Race Condition, Mutex, Java Volatile & Deadlock в Java:
  В среда с много нишки, в която участват множество нишки, непременно ще има сблъсъци, когато повече от една нишка се опитват да получат един и същ ресурс едновременно. Тези сблъсъци водят до "състояние на състезанието" и по този начин програмата дава неочаквани резултати.
  Например, един файл се актуализира от две нишки. Ако една нишка T1 е в процес на актуализиране на този файл, кажете някаква променлива. Сега, докато тази актуализация от T1 все още е в ход, да кажем, че втората нишка T2 също актуализира същата променлива. По този начин променливата ще даде грешни резултати.
  => Тук вижте пълната серия за обучение по Java.
  Когато са включени множество нишки, трябва да управляваме тези нишки по такъв начин, че даден ресурс може да бъде достъпен от една нишка наведнъж. В горния пример файлът, до който има достъп и от двете нишки, трябва да се управлява по такъв начин, че T2 няма достъп до файла, докато T1 не приключи с него.
  Това се прави в Java, като се използва " Синхронизация на нишки ".
  Синхронизация на нишки в Java
  Състезание в Java
  Брави / монитори в Java
  Mutexes в Java
  Синхронизирана ключова дума
  Видове синхронизация
  Многопоточност без синхронизация
  Блокиран синхронизиран код
  Многопоточност със синхронизация
  Синхронизиран метод
  Летливи в Java
  Тупик в Java
  Тъй като Java е език с много нишки, синхронизирането на нишки има голямо значение в Java, тъй като множество нишки се изпълняват паралелно в приложение.
  Използваме ключови думи "Синхронизиран" и 'летлив' за постигане на синхронизация в Java
  Нуждаем се от синхронизация, когато споделеният обект или ресурс е променлив. Ако ресурсът е неизменен, тогава нишките ще четат ресурса само едновременно или поотделно.
  В този случай не е необходимо да синхронизираме ресурса. В този случай JVM гарантира това Синхронизираният код на Java се изпълнява по една нишка наведнъж .
  В повечето случаи едновременният достъп до споделени ресурси в Java може да доведе до грешки като "Несъответствие на паметта" и "смущения в нишките". За да избегнем тези грешки, трябва да използваме синхронизиране на споделени ресурси, така че достъпът до тези ресурси да се изключва взаимно.
  Използваме концепция, наречена Монитори за реализиране на синхронизация. Мониторът може да бъде достъпен само по една нишка наведнъж. Когато нишка получи ключалката, тогава можем да кажем, че нишката е влязла в монитора.
  Когато достъпът до монитор се осъществява от определена нишка, мониторът се заключва и всички останали нишки, опитващи се да влязат в монитора, се спират, докато нишката за достъп завърши и освободи заключването.
  Занапред ще обсъдим подробно синхронизирането в Java в този урок. Сега, нека обсъдим някои основни концепции, свързани със синхронизацията в Java.
  В многонишкова среда, когато повече от една нишка се опитва да получи достъп до споделен ресурс за едновременно писане, тогава множество нишки се състезават помежду си, за да завършат достъпа до ресурса. Това поражда "състояние на състезанието".
  Едно нещо, което трябва да имате предвид, е, че няма проблем, ако множество нишки се опитват да получат достъп до споделен ресурс само за четене. Проблемът възниква, когато множество нишки имат достъп до един и същи ресурс едновременно.
  Състезателните условия възникват поради липса на правилна синхронизация на нишките в програмата. Когато правилно синхронизираме нишките така, че в даден момент само една нишка ще има достъп до ресурса и състоянието на състезанието престава да съществува.
  И така, как да открием Състезателното състояние?
  Най-добрият начин за откриване на състоянието на състезанието е чрез преглед на кода. Като програмист, трябва да прегледаме кода щателно, за да проверим за потенциални условия на състезанието, които могат да възникнат.
  Вече споменахме, че използваме монитори или брави за реализиране на синхронизация. Мониторът или заключването е вътрешен обект и е свързан с всеки обект. Така че, когато нишката се нуждае от достъп до обекта, тя първо трябва да придобие ключалката или монитора на своя обект, да работи върху обекта и след това да освободи ключалката.
  Бравите в Java ще изглеждат, както е показано по-долу:
  public class Lock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while(isLocked) { wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
  Както е показано по-горе, имаме метод lock (), който заключва екземпляра. Всички нишки, извикващи метода lock (), ще бъдат блокирани, докато наборите на метода unblock () не бъдат заключени като флаг и не уведомят всички чакащи нишки.
  Някои указатели, които да запомните за ключалките:
  В Java всеки обект има ключалка или монитор. Тази ключалка може да бъде достъпна чрез нишка.
  В даден момент само една нишка може да придобие този монитор или да заключи.
  Java езикът за програмиране предоставя ключова дума Synchronized ', която ни позволява да синхронизираме нишките, като направим блок или метод като Synchronized.
  Споделените ресурси, до които трябва да имат достъп нишките, се съхраняват под този синхронизиран блок / метод.
  Вече обсъдихме, че в многопоточна среда могат да възникнат условия на състезание, когато повече от една нишка се опитва да осъществи достъп до споделените ресурси едновременно и условията на състезанието водят до неочакван изход.
  Частта от програмата, която се опитва да получи достъп до споделения ресурс, се нарича "Критичен раздел" . За да се избегне появата на състезателни условия, е необходимо да се синхронизира достъпът до критичната секция. Синхронизирайки тази критична секция, ние се уверяваме, че само една нишка може да има достъп до критичната секция наведнъж.
  Най-простият тип синхронизатор е "mutex". Mutex гарантира, че във всеки даден случай само една нишка може да изпълни критичната секция.
  Мютексът е подобен на концепцията за монитори или брави, която обсъдихме по-горе. Ако нишката трябва да получи достъп до критична секция, тогава тя трябва да придобие мютекса. След като мутексът бъде придобит, нишката ще получи достъп до кода на критичната секция и когато приключи, ще освободи мутекса.
  Междувременно останалите нишки, които чакат за достъп до критичната секция, ще бъдат блокирани. Веднага след като нишката, която държи мютекса, го освободи, друга нишка ще влезе в критичния раздел.
  Има няколко начина, по които можем да внедрим мютекс в Java.
  Използване на синхронизирана ключова дума
  Използване на Semaphore
  Използване на ReentrantLock
  В този урок ще обсъдим първия подход, т.е.синхронизация. Другите два подхода - Semaphore и ReentrantLock ще бъдат обсъдени в следващия урок, в който ще обсъдим едновременния пакет на Java.
  Java предоставя ключова дума "Синхронизирано", която може да се използва в програма за маркиране на критичен раздел. Критичният раздел може да бъде блок с код или пълен метод. По този начин само една нишка може да осъществи достъп до критичната секция, маркирана от ключовата дума Synchronized.
  Можем да напишем едновременните части (части, които се изпълняват едновременно) за приложение, използвайки ключовата дума Synchronized. Също така се отърваваме от състезателните условия, като правим блок от код или метод Синхронизиран.
  Когато маркираме блок или метод синхронизирани, ние защитаваме споделените ресурси в тези обекти от едновременен достъп и по този начин повреда.
  Има 2 вида синхронизация, както е обяснено по-долу:
  # 1) Синхронизиране на процеса
  Синхронизацията на процеси включва множество процеси или нишки, изпълняващи се едновременно. В крайна сметка те достигат състояние, при което тези процеси или нишки се ангажират с определена последователност от действия.
  # 2) Синхронизация на нишки
  В синхронизацията на нишки, повече от една нишка се опитва да получи достъп до споделено пространство. Нишките се синхронизират по такъв начин, че споделеното пространство е достъпно само от една нишка наведнъж.
  Синхронизацията на процеси е извън обхвата на този урок. Следователно тук ще обсъждаме само синхронизацията на нишките.
  В Java можем да използваме синхронизираната ключова дума с:
  Блок код
  Горните типове са взаимно изключващи се видове синхронизация на нишки. Взаимното изключване пречи на нишките с достъп до споделени данни да се намесват помежду си.
  Другият тип синхронизация на нишки е "InterThread комуникация", която се основава на сътрудничеството между нишките. Комуникацията между нишки е извън обхвата на този урок.
  Преди да продължим със синхронизирането на блокове и методи, нека внедрим Java програма, за да демонстрираме поведението на нишките, когато няма синхронизация.
  Следващата програма Java има множество нишки, които не са синхронизирани.
  class PrintCount { //method to print the thread counter public void printcounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run method for thread public void run() { PD.printcounter(); System.out.println('Thread ' + threadName + ' exiting.'); } //start method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args[]) { PrintCount PD = new PrintCount(); //create two instances of thread class ThreadCounter T1 = new ThreadCounter( 'ThreadCounter_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'ThreadCounter_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
  От изхода можем да видим, че тъй като нишките не са синхронизирани, изходът е несъвместим. И двете нишки започват и след това те показват брояча една след друга. И двете нишки излизат в края.
  От дадената програма първата нишка трябва да е излязла след показване на стойностите на брояча, а след това втората нишка трябва да започне да показва стойностите на брояча.
  Сега нека да отидем за синхронизация и да започнем с синхронизацията на кодовия блок.
  Синхронизиран блок се използва за синхронизиране на блок от код. Този блок обикновено се състои от няколко реда. Синхронизиран блок се използва, когато не искаме да се синхронизира цял метод.
  Например, имаме метод с да речем 75 реда код. От това само 10 реда код трябва да бъдат изпълнени от една нишка наведнъж. В този случай, ако направим целия метод синхронизиран, това ще бъде в тежест за системата. В такива ситуации се насочваме към синхронизирани блокове.
  Обхватът на синхронизирания метод винаги е по-малък от този на синхронизирания метод. Синхронизираният метод заключва обект от споделен ресурс, който трябва да се използва от множество нишки.
  Общият синтаксис на синхронизиран блок е както е показано по-долу:
  synchronized (lock_object){ //synchronized code statements }
  Тук "lock_object" е референтен израз на обект, върху който трябва да се получи заключването. Така че, когато нишката иска да осъществи достъп до синхронизираните отчети в блока за изпълнение, тя трябва да придобие заключването на монитора 'lock_object'.
  Както вече беше обсъдено, синхронизираната ключова дума гарантира, че само една нишка може да придобие ключалка наведнъж, а всички останали нишки трябва да изчакат, докато нишката, която държи ключалката, завърши и освободи ключалката.
  Хвърля се "NullPointerException", ако използваният lock_object е Null.
  Ако конец спи, докато все още държи ключалката, ключалката не се освобождава. Другите нишки няма да имат достъп до споделения обект през това време на заспиване.
  Сега ще представим горния пример, който вече е реализиран с леки промени. В по-ранната програма не сме синхронизирали кода. Сега ще използваме синхронизирания блок и ще сравним изхода.
  В програмата Java по-долу използваме синхронизиран блок. В метода за изпълнение ние синхронизираме кода от редове, които отпечатват брояча за всяка нишка.
  class PrintCount { //print thread counter public void printCounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run () method for thread with synchronized block public void run() { synchronized(PD) { PD.printCounter(); } System.out.println('Thread ' + threadName + ' exiting.'); } //start () method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args[]) { PrintCount PD = new PrintCount(); //create thread instances ThreadCounter T1 = new ThreadCounter( 'Thread_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'Thread_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
  Сега изходът на тази програма с помощта на синхронизиран блок е доста последователен. Както се очаква, и двете нишки започват да се изпълняват. Първата нишка завърши, показва стойностите на брояча и излиза. След това втората нишка показва стойностите на брояча и излиза.
  Нека обсъдим синхронизирания метод в този раздел. По-рано видяхме, че можем да декларираме малък блок, състоящ се от по-малко кодови редове, като синхронизиран блок. Ако искаме цялата функция да бъде синхронизирана, тогава можем да декларираме метод като синхронизиран.
  Когато методът се синхронизира, тогава само една нишка ще може да извършва извикване на метод наведнъж.
  Общият синтаксис за писане на синхронизиран метод е:
  synchronized method_name (parameters){ //synchronized code }
  Подобно на синхронизиран блок, в случай на синхронизиран метод, ние се нуждаем от lock_object, който ще бъде използван от нишки, осъществяващи достъп до синхронизирания метод.
  За синхронизирания метод заключеният обект може да бъде един от следните:
  Ако синхронизираният метод е статичен, тогава обектът за заключване се дава от обект '.class'.
  За нестатичен метод, заключеният обект се дава от текущия обект, т.е. 'този' обект.
  Особена черта на синхронизираната ключова дума е, че тя се връща отново. Това означава, че синхронизиран метод може да извика друг синхронизиран метод със същото заключване. Така нишката, която държи ключалката, може да получи достъп до друг синхронизиран метод, без да се налага да придобива различна ключалка.
  Синхронизираният метод е демонстриран, като се използва примера по-долу.
  class NumberClass { //synchronized method to print squares of numbers synchronized void printSquares(int n) throws InterruptedException { //iterate from 1 to given number and print the squares at each iteration for (int i = 1; i <= n; i++) { System.out.println(Thread.currentThread().getName() + ' :: '+ i*i); Thread.sleep(500); } } } public class Main { public static void main(String args[]) { final NumberClass number = new NumberClass(); //create thread Runnable thread = new Runnable() { public void run() { try { number.printSquares(3); } catch (InterruptedException e) { e.printStackTrace(); } } }; //start thread instance new Thread(thread, 'Thread One').start(); new Thread(thread, 'Thread Two').start(); } }
  В горната програма използвахме синхронизиран метод за отпечатване на квадратите на число. Горната граница на числото се предава на метода като аргумент. След това, започвайки от 1, квадратите на всяко число се отпечатват до достигане на горната граница.
  В основната функция се създава екземпляр на нишката. На всеки екземпляр на нишка се предава число за отпечатване на квадрати.
  Както бе споменато по-горе, когато методът, който трябва да се синхронизира, е статичен, тогава обектът заключване участва в класа, а не обектът. Това означава, че ще заключим класа, а не обекта. Това се нарича статична синхронизация.
  Друг пример е даден по-долу.
  class Table{ //synchronized static method to print squares of numbers synchronized static void printTable(int n){ for(int i=1;i<=10;i++){ System.out.print(n*i + ' '); try{ Thread.sleep(400); }catch(Exception e){} } System.out.println(); } } //thread class Thread_One class Thread_One extends Thread{ public void run(){ Table.printTable(2); } } //thread class Thread_Two class Thread_Two extends Thread{ public void run(){ Table.printTable(5); } } public class Main{ public static void main(String t[]){ //create instances of Thread_One and Thread_Two Thread_One t1=new Thread_One (); Thread_Two t2=new Thread_Two (); //start each thread instance t1.start(); t2.start(); } }
  В горната програма отпечатваме таблици за умножение на числа. Всяко число, чиято таблица ще бъде отпечатана, е екземпляр на нишка от различен клас нишка. По този начин отпечатваме таблици за умножение на 2 и 5, така че имаме два класа thread_one и thread_two, за да отпечатаме таблиците 2 и 5 съответно.
  За да обобщим, синхронизираната ключова дума Java изпълнява следните функции:
  Синхронизираната ключова дума в Java гарантира взаимно изключващ се достъп до споделени ресурси, като осигурява заключващ механизъм. Заключването също така предотвратява състезателни условия.
  Използвайки синхронизираната ключова дума, ние предотвратяваме едновременни грешки при програмиране в кода.
  Когато метод или блок е деклариран като синхронизиран, тогава нишката се нуждае от изключителна ключалка, за да влезе в синхронизирания метод или блок. След извършване на необходимите действия нишката освобождава ключалката и ще изтрие операцията за запис. По този начин ще премахне грешките в паметта, свързани с несъответствие.
  Летлива ключова дума в Java се използва, за да направи класовете безопасни за нишки. Също така използваме ключовата дума volatile, за да модифицираме стойността на променливата чрез различни нишки. Волатилна ключова дума може да се използва за деклариране на променлива с примитивни типове, както и обекти.
  В някои случаи нестабилна ключова дума се използва като алтернатива на синхронизираната ключова дума, но имайте предвид, че тя не е заместител на синхронизираната ключова дума.
  Когато дадена променлива е обявена за нестабилна, нейната стойност никога не се кешира, а винаги се чете от основната памет. Летлива променлива гарантира подреждане и видимост. Въпреки че променлива може да бъде декларирана като нестабилна, не можем да декларираме класове или методи като нестабилни.
  Помислете за следния блок код:
  class ABC{ static volatile int myvar =10; }
  В горния код променливата myvar е статична и нестабилна. Статична променлива се споделя между всички обекти на класа. Летливата променлива винаги се намира в основната памет и никога не се кешира.
  Следователно в основната памет ще има само едно копие на myvar и всички действия за четене / запис ще се извършват върху тази променлива от основната памет. Ако myvar не беше деклариран като летлив, тогава всеки обект на нишка ще има различно копие, което би довело до несъответствия.
  Някои от разликите между летливи и синхронизирани ключови думи са изброени по-долу.
  Волатилна ключова дума
  Волатилната ключова дума се използва само с променливи. Синхронизираната ключова дума се използва с кодови блокове и методи.
  Летлива ключова дума не може да блокира нишката за изчакване. Синхронизираната ключова дума може да блокира нишката за изчакване.
  Ефективността на нишката се подобрява с Volatile. Производителността на нишките се влошава донякъде при синхронизирането.
  Летливите променливи се намират в основната памет. Синхронизираните конструкции не се намират в основната памет.
  Volatile синхронизира по една променлива между паметта на нишката и основната памет наведнъж. Синхронизирана ключова дума синхронизира всички променливи наведнъж.
  Видяхме, че можем да синхронизираме множество нишки с помощта на синхронизирана ключова дума и да направим програмите безопасни за нишки. Чрез синхронизиране на нишките гарантираме, че множеството нишки се изпълняват едновременно в среда с много нишки.
  Понякога обаче възниква ситуация, при която нишките вече не могат да функционират едновременно. Вместо това те чакат безкрайно. Това се случва, когато една нишка чака на ресурс и този ресурс е блокиран от втората нишка.
  Втората нишка, от друга страна, чака в ресурса, който е блокиран от първата нишка. Подобна ситуация поражда "задънена улица" в Java.
  Deadlock в Java е изобразен с помощта на изображението по-долу.
  Както можем да видим от горната диаграма, нишка A е заключила ресурса r1 и чака ресурс r2. Нишката B, от друга страна, е блокирала ресурса r2 и чака на r1.
  По този начин никоя от нишките не може да завърши изпълнението си, освен ако не се добере до чакащите ресурси. Тази ситуация доведе до блокиране, при което и двете нишки чакат безкрайно ресурсите.
  Даден по-долу е пример за Deadlocks в Java.
  public class Main { public static void main(String[] args) { //define shared resources final String shared_res1 = 'Java tutorials'; final String shared_res2 = 'Multithreading'; // thread_one => locks shared_res1 then shared_res2 Thread thread_one = new Thread() { public void run() { synchronized (shared_res1) { System.out.println('Thread one: locked shared resource 1'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res2) { System.out.println('Thread one: locked shared resource 2'); } } } }; // thread_two=> locks shared_res2 then shared_res1 Thread thread_two = new Thread() { public void run() { synchronized (shared_res2) { System.out.println('Thread two: locked shared resource 2'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res1) { System.out.println('Thread two: locked shared resource 1'); } } } }; //start both the threads thread_one.start(); thread_two.start(); } }
  В горната програма имаме два споделени ресурса и две нишки. И двете нишки се опитват да получат достъп до споделените ресурси една по една. Резултатът показва и двете нишки, които заключват по един ресурс, докато чакат останалите. По този начин се създава ситуация на задънена улица.
  Въпреки че не можем да спрем напълно да се случват ситуации на задънена улица, със сигурност можем да ги избегнем, като предприемем някои стъпки.
  По-долу са изброени средствата, чрез които можем да избегнем блокировки в Java.
  # 1) Чрез избягване на вложени ключалки
  Наличието на вложени ключалки е най-важната причина за блокиране. Вложени ключалки са ключалките, които се дават на множество нишки. По този начин трябва да избягваме да даваме ключалки на повече от една нишка.
  # 2) Използвайте нишка Join
  Трябва да използваме Thread.join с максимално време, за да могат нишките да използват максималното време за изпълнение. Това ще предотврати блокиране, което се случва най-вече, тъй като една нишка непрекъснато чака други.
  # 3) Избягвайте ненужното заключване
  Трябва да заключим само необходимия код. Наличието на ненужни ключалки за кода може да доведе до блокировки в програмата. Тъй като мъртвата блокировка може да наруши кода и да попречи на потока на програмата, трябва да сме склонни да избягваме мъртвата блокировка в нашите програми.
  В # 1) Какво е синхронизация и защо е важно?
  Отговор: Синхронизацията е процес на контрол на достъпа на споделен ресурс до множество нишки. Без синхронизация множество нишки могат да актуализират или променят споделения ресурс едновременно, което води до несъответствия.
  По този начин трябва да гарантираме, че в среда с много нишки нишките се синхронизират, така че начинът, по който имат достъп до споделените ресурси, да се взаимно изключват и последователно.
  В # 2) Какво е синхронизация и несинхронизация в Java?
  Отговор: Синхронизацията означава, че конструкцията е безопасна за нишки. Това означава, че множество нишки не могат да имат достъп до конструкцията (кодов блок, метод и т.н.) наведнъж.
  Несинхронизираните конструкции не са безопасни за нишки. Няколко нишки могат по всяко време да имат достъп до несинхронизираните методи или блокове. Популярен несинхронизиран клас в Java е StringBuilder.
  В # 3) Защо се изисква синхронизация?
  Отговор: Когато процесите трябва да се изпълняват едновременно, ние се нуждаем от синхронизация. Това е така, защото се нуждаем от ресурси, които могат да се споделят между много процеси.
  За да избегнем сблъсъци между процеси или нишки за достъп до споделени ресурси, трябва да синхронизираме тези ресурси, така че всички нишки да получат достъп до ресурси и приложението също да работи безпроблемно.
  В # 4) Как да получите синхронизиран ArrayList?
  Отговор: Можем да използваме Collections.synchronized list метод с ArrayList като аргумент за конвертиране на ArrayList в синхронизиран списък.
  В # 5) Синхронизиран ли е HashMap?
  Отговор: Не, HashMap не се синхронизира, но HashTable се синхронизира.
  В този урок обсъдихме подробно синхронизирането на нишки. Заедно с това научихме и за променливата ключова дума и блокировки в Java. Синхронизацията се състои от синхронизация на процеси и нишки.
  В среда с много нишки ние сме по-загрижени за синхронизирането на нишките. Тук видяхме подхода на синхронизираната ключова дума за синхронизация на нишки.
  Deadlock е ситуация, при която множество нишки безкрайно чакат ресурси. Видяхме примера на блокировки в Java заедно с методите за избягване на блокировки в Java.