здрасти Днес ще продължим да разглеждаме функциите на многонишковото програмиране и ще говорим за синхронизиране на нишки.
Причината се крие във факта, че нишките работят със споделен ресурс (конзолата), без да координират действията си помежду си. Ако планировчикът на нишки разпредели време за Thread-1, тогава той моментално записва всичко в конзолата. Какво други теми вече са успели да напишат or не, няма meaning. Резултатът, Howто виждате, е отчайващ. Ето защо те въведоха специална концепция, mutex (взаимно изключване) в многопоточното програмиране. Целта на mutexе да осигури механизъм, така че само една нишка да има достъп до обект в определен момент. Ако Thread-1 придобие mutex на обект A, другите нишки няма да имат достъп и да променят обекта. Другите нишки трябва да изчакат, докато мютексът на обект А бъде освободен. Ето пример от живота: представете си, че вие и още 10 непознати участвате в упражнение. Редувайки се, трябва да изразите идеите си и да обсъдите нещо. Но тъй като се виждате за първи път, за да не се прекъсвате непрекъснато и да се ядосвате, използвате „говореща топка“: само човекът с топката може да говори. По този начин ще имате добра и ползотворна дискусия. По същество топката е мютекс. Ако mutex на обект е в ръцете на една нишка, други нишки не могат да работят с обекта.
Между другото! По време на курса вече сте виждали примери за

Какво е синхронизация в Java?
Извън областта на програмирането, това предполага подреждане, което позволява на две устройства or програми да работят заедно. Например смартфон и компютър могат да се синхронизират с акаунт в Google, а акаунт в уебсайт може да се синхронизира с акаунти в социални мрежи, така че да можете да ги използвате за влизане. Синхронизирането на нишки има подобно meaning: това е подреждане, при което нишките взаимодействат с взаимно. В предишните уроци нишките ни живееха и работеха отделно една от друга. Един извършваше изчисления, втори спеше, а трети показваше нещо на конзолата, но не си взаимодействаха. В реалните програми такива ситуации са рядкост. Множество нишки могат активно да работят и да променят един и същ набор от данни. Това създава проблеми. Представете си множество нишки, които пишат текст на едно и също място, например в текстов файл or конзолата. В този случай файлът or конзолата стават споделен ресурс. Нишките не знаят за съществуването на другия, така че те просто пишат всичко, което могат във времето, определено им от планировчика на нишки. В скорошен урок видяхме пример накъде води това. Нека си го припомним сега:
Object
клас, което означава, че всеки обект в Java има такъв.
Как работи синхронизираният оператор
Нека се запознаем с нова ключова дума: синхронизирано . Използва се за маркиране на определен блок от code. Ако codeов блок е маркиран сsynchronized
ключовата дума, тогава този блок може да бъде изпълнен само от една нишка наведнъж. Синхронизацията може да се реализира по различни начини. Например, чрез деклариране на цял метод за синхронизиране:
public synchronized void doSomething() {
// ...Method logic
}
Или напишете codeов блок, където синхронизирането се извършва с помощта на няHowъв обект:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
Значението е просто. Ако една нишка влезе в codeовия блок, маркиран с synchronized
ключовата дума, тя незабавно улавя мутекса на обекта и всички други нишки, опитващи се да влязат в същия блок or метод, са принудени да изчакат, докато предишната нишка завърши работата си и освободи монитора. 
synchronized
, но те изглеждаха различно:
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
Темата е нова за вас. И, разбира се, ще има объркване със синтаксиса. Така че, запомнете го веднага, за да избегнете по-късно объркване от различните начини на писане. Тези два начина на писане означават едно и също нещо:
public void swap() {
synchronized (this)
{
// ...Method logic
}
}
public synchronized void swap() {
}
}
В първия случай вие създавате синхронизиран блок от code веднага след въвеждане на метода. Синхронизира се от this
обекта, т.е. текущия обект. И във втория пример прилагате synchronized
ключовата дума към целия метод. Това прави ненужно изричното посочване на обекта, който се използва за синхронизация. Тъй като целият метод е маркиран с ключовата дума, методът автоматично ще се синхронизира за всички екземпляри на класа. Няма да се впускаме в дискусия за това кой начин е по-добър. Засега изберете Howъвто начин ви харесва най-добре :) Основното нещо е да запомните: можете да декларирате метод синхронизиран само когато цялата му логика се изпълнява от една нишка наведнъж. Например, би било грешка да направите следния doSomething()
метод синхронизиран:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
Както можете да видите, част от метода съдържа логика, която не изисква синхронизация. Този code може да се изпълнява от множество нишки едновременно и всички критични места са отделени в отделен synchronized
блок. И още нещо. Нека разгледаме внимателно нашия пример от урока със смяна на имена:
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
Забележка: синхронизирането се извършва с помощта наthis
. Тоест с помощта на конкретенMyClass
обект. Да предположим, че имаме 2 нишки (Thread-1
иThread-2
) и само единMyClass myClass
обект. В този случай, акоThread-1
извикаmyClass.swap()
метода, мутексът на обекта ще бъде зает и при опит за извикване методътmyClass.swap()
щеThread-2
виси, докато чака мутексът да бъде освободен. Ако ще имаме 2 нишки и 2MyClass
обекта (myClass1
иmyClass2
), нашите нишки могат лесно едновременно да изпълняват синхронизираните методи на различни обекти. Първата нишка изпълнява това:
myClass1.swap();
Вторият изпълнява това:
myClass2.swap();
В този случай synchronized
ключовата дума в swap()
метода няма да повлияе на работата на програмата, тъй като синхронизирането се извършва с помощта на конкретен обект. И в последния случай имаме 2 обекта. Така нишките не създават проблеми една на друга. В края на краищата два обекта имат 2 различни мутекса и придобиването на единия е независимо от придобиването на другия .
Особености на синхронизацията при статичните методи
Но Howво ще стане, ако трябва да синхронизирате статичен метод ?
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static synchronized void swap() {
String s = name1;
name1 = name2;
name2 = s;
}
}
Не е ясно Howва роля ще играе mutex тук. В крайна сметка вече установихме, че всеки обект има мютекс. Но проблемът е, че не се нуждаем от обекти, за да извикаме MyClass.swap()
метода: методът е статичен! И така, Howво следва? :/ Тук всъщност няма проблем. Създателите на Java са се погрижor за всичко :) Ако метод, който съдържа критична паралелна логика, е статичен, тогава синхронизацията се извършва на ниво клас. За по-голяма яснота можем да пренапишем горния code, Howто следва:
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static void swap() {
synchronized (MyClass.class) {
String s = name1;
name1 = name2;
name2 = s;
}
}
}
По принцип бихте могли сами да се сетите за това: Тъй като няма обекти, механизмът за синхронизация трябва по няHowъв начин да бъде вграден в самия клас. И това е така: можем да използваме класове за синхронизиране.
GO TO FULL VERSION