JavaRush /Java блогы /Random-KK /Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау
Viacheslav
Деңгей

Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау

Топта жарияланған

Кіріспе

Сонымен, біз Java-да ағындар бар екенін білеміз, олар туралы « Жаваны жіппен бүлдіре алмайсыз: I бөлім - тақырыптар » шолуынан оқуға болады . Жұмысты бір уақытта орындау үшін жіптер қажет. Сондықтан жіптердің бір-бірімен қандай да бір түрде әрекеттесуі өте ықтимал. Мұның қалай болатынын және бізде қандай негізгі бақылаулар бар екенін анықтайық. Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 1

Өткізіп жібер

Thread.yield() әдісі жұмбақ және сирек қолданылады. Интернетте оның сипаттамасының көптеген нұсқалары бар. Кейбіреулер олардың басымдықтарын ескере отырып, ағын төмен жылжитын ағындардың кезегі туралы жазады. Біреу ағын өзінің күйін іске қосылған күйден іске қосылатын күйге өзгертетінін жазады (бірақ бұл күйлерге ешқандай бөлу жоқ, ал Java оларды ажыратпайды). Бірақ іс жүзінде бәрі әлдеқайда белгісіз және белгілі бір мағынада қарапайым. Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 2Әдіс құжаттамасы тақырыбында " JDK-6416721: (ерекше ағын) Fix Thread.yield() javadocyield " қатесі бар . Егер сіз оны оқысаңыз, шын мәнінде әдіс Java ағынын жоспарлаушыға бұл ағынды орындауға аз уақыт беруге болатыны туралы кейбір ұсыныстарды ғана беретіні анық . Бірақ іс жүзінде не болады, жоспарлаушы ұсынысты ести ме және оның не істейтіні JVM мен операциялық жүйенің іске асырылуына байланысты. Немесе басқа факторларға байланысты болуы мүмкін. Барлық шатасулар Java тілін дамыту кезінде көп ағынды қайта қарауға байланысты болуы мүмкін. Толығырақ " Java Thread.yield() қысқаша кіріспе " шолудан оқи аласыз . yield

Ұйқы - ұйықтап жатқан жіп

Жіп оны орындау кезінде ұйықтап кетуі мүмкін. Бұл басқа ағындармен әрекеттесудің ең қарапайым түрі. Java виртуалды машинасы орнатылған, Java codeы орындалатын операциялық жүйеде Thread Scheduler деп аталатын өзінің ағынды жоспарлаушысы бар. Қай жіпті қашан іске қосу керектігін өзі шешеді. Бағдарламалаушы бұл жоспарлаушымен тікелей Java codeынан өзара әрекеттесе алмайды, бірақ ол JVM арқылы жоспарлаушыдан ағынды біраз уақытқа кідіртуді, оны «ұйқы режиміне қоюды» сұрай алады. Толығырақ " Thread.sleep() " және " Multithreading қалай жұмыс істейді " мақалаларынан оқи аласыз . Сонымен қатар, Windows ОЖ жүйесінде ағындардың қалай жұмыс істейтінін білуге ​​болады: « Windows Thread ішкі элементтері ». Енді оны өз көзімізбен көретін боламыз. Келесі codeты файлға сақтайық HelloWorldApp.java:
class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Waked up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Көріп отырғаныңыздай, бізде 60 секунд күтетін тапсырма бар, содан кейін бағдарлама аяқталады. Біз құрастырамыз javac HelloWorldApp.javaжәне іске қосамыз java HelloWorldApp. Бөлек терезеде іске қосқан дұрыс. Мысалы, Windows жүйесінде бұл келесідей болады: start java HelloWorldApp. jps пәрменін пайдалана отырып, процестің PID codeын анықтаймыз және ағындар тізімін келесі арқылы ашамыз jvisualvm --openpid pidПроцесса: Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 3Көріп отырғаныңыздай, біздің ағын ұйқы күйіне өтті. Шын мәнінде, ағымдағы жіпті ұйықтау әлдеқайда әдемі болуы мүмкін:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Waked up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Біз барлық жерде өңдейтінімізді байқаған шығарсыз InterruptedException? Неге екенін түсінейік.

Жіпті үзу немесе Thread.interrupt

Мәселе мынада, жіп ұйқыда күтіп тұрғанда, біреу бұл күтуді тоқтатқысы келуі мүмкін. Бұл жағдайда біз мұндай ерекшелікті өңдейміз. Thread.stopБұл әдіс ескірген деп жарияланғаннан кейін жасалды , яғни. ескірген және пайдалану қажет емес. Мұның себебі, әдісті шақырған кезде stopжіп жай ғана «өлтірілді», бұл өте күтпеген еді. Біз ағынның қашан тоқтайтынын біле алмадық, деректердің сәйкестігіне кепілдік бере алмадық. Сіз файлға деректерді жазып жатырсыз, содан кейін ағын жойылады деп елестетіңіз. Сондықтан олар ағынды өлтірмей, оны үзу керек деп хабарлау қисындырақ деп шешті. Бұған қалай әрекет ету ағынның өзіне байланысты. Қосымша мәліметтерді Oracle компаниясының " Thread.stop неге ескірген? " Мысал қарастырайық:
public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Бұл мысалда біз 60 секунд күтпейміз, бірақ бірден «Үзілген» деп басып шығарамыз. Себебі біз ағын әдісі деп атадық interrupt. Бұл әдіс «үзу күйі деп аталатын ішкі жалауды» орнатады. Яғни, әрбір жіп тікелей қол жеткізуге болмайтын ішкі жалаушаға ие. Бірақ бізде бұл жалаумен әрекеттесу үшін жергілікті әдістер бар. Бірақ бұл жалғыз жол емес. Жіп орындалу процесінде болуы мүмкін, бірдеңені күтпей, жай ғана әрекеттерді орындайды. Бірақ бұл олардың жұмысының белгілі бір кезеңінде оны аяқтағысы келетінін қамтамасыз ете алады. Мысалы:
public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			//Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Жоғарыдағы мысалда цикл whileсырттан үзілгенше орындалатынын көруге болады. isInterrupted жалаушасы туралы білу маңызды нәрсе , егер біз оны ұстасақ InterruptedException, жалауша isInterruptedқалпына келтіріледі, содан кейін isInterruptedол false мәнін қайтарады. Сондай-ақ Thread сыныбы үшін тек ағымдағы ағынға қолданылатын статикалық әдіс бар - Thread.interrupted() , бірақ бұл әдіс жалаушаны "false" мәніне қайтарады! Толығырақ « Жіпті үзу » тарауынан оқи аласыз .

Қосылу — Басқа ағынның аяқталуын күтуде

Күтудің ең қарапайым түрі - басқа ағынның аяқталуын күту.
public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Бұл мысалда жаңа ағын 5 секунд ұйықтайды. Бұл кезде негізгі жіп ұйықтап жатқан жіп оянып, жұмысын аяқтағанша күтеді. JVisualVM арқылы қарасаңыз, ағынның күйі келесідей болады: Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 4Бақылау құралдарының арқасында ағынмен не болып жатқанын көруге болады. Әдіс joinөте қарапайым, себебі бұл жай ғана java codeы бар әдіс, ол waitшақырылған ағын тірі кезінде орындалады. Жіп өлгеннен кейін (тоқтату кезінде) күту тоқтатылады. Бұл әдістің бүкіл сиқыры join. Сондықтан, ең қызықты бөлігіне көшейік.

Монитор тұжырымдамасы

Көп ағында Монитор деген нәрсе бар. Жалпы, монитор сөзі латын тілінен аударғанда «қадағалаушы» немесе «бақылаушы» дегенді білдіреді. Осы мақаланың аясында біз мәнін есте сақтауға тырысамыз, ал қалайтындар үшін егжей-тегжейлі ақпарат алу үшін сілтемелерден материалға енуіңізді сұраймын. Саяхатымызды Java тілінің спецификациясынан, яғни JLS арқылы бастайық: " 17.1. Синхрондау ". Онда мыналар айтылады: Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 5Жіптер арасында синхрондау мақсатында Java «Монитор» деп аталатын белгілі бір механизмді пайдаланады. Әрбір нысанда онымен байланыстырылған монитор бар және ағындар оны құлыптауы немесе құлпын ашуы мүмкін. Әрі қарай, біз Oracle веб-сайтында оқу құралын табамыз: « Ішкі құлыптар және синхрондау ». Бұл оқулық Java жүйесінде синхрондау ішкі құлып немесе монитор құлпы деп аталатын ішкі нысанның айналасында құрылатынын түсіндіреді. Көбінесе мұндай құлып жай ғана «монитор» деп аталады. Сондай-ақ, біз Java-дағы әрбір нысанның онымен байланысты ішкі құлыпқа ие екенін тағы да көреміз. Сіз « Java - ішкі құлыптар мен синхрондау » оқуға болады . Әрі қарай, Java тіліндегі нысанды монитормен қалай байланыстыруға болатындығын түсіну маңызды. Java тіліндегі әрбір нысанның тақырыбы бар - codeтан бағдарламашыға қол жетімді емес, бірақ виртуалды машина нысандармен дұрыс жұмыс істеу үшін қажет ішкі метадеректер түрі. Нысан тақырыбына келесідей MarkWord кіреді: Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Мұнда Хабр мақаласы өте пайдалы: " Бірақ көп ағынды пайдалану қалай жұмыс істейді? I бөлім: синхрондау ." Бұл мақалаға JDK қате жіберушісінің тапсырмалар блогының қысқаша сипаттамасынан сипаттаманы қосқан жөн: “ JDK-8183909 ”. Дәл сол нәрсені " JEP-8183909 " бөлімінен оқуға болады . Сонымен, Java-да монитор an objectімен байланысты және ағын бұл ағынды блоктай алады немесе олар «құлыпты алу» деп те айтады. Ең қарапайым мысал:
public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Сонымен, кілт сөзді пайдалана отырып, synchronizedағымдағы ағын (осы code жолдары орындалатын) нысанмен байланысты мониторды пайдалануға тырысады objectжәне «құлып алу» немесе «мониторды басып алу» (екінші нұсқа тіпті жақсырақ). Мониторға қатысты дау болмаса (яғни сол нысанда басқа ешкім синхрондауды қаламаса), Java «біржақты құлыптау» деп аталатын оңтайландыруды орындауға тырысуы мүмкін. Mark Word бағдарламасындағы нысанның тақырыбы сәйкес тег пен монитор қай ағынға тіркелген жазбадан тұрады. Бұл мониторды түсіру кезіндегі үстеме шығынды азайтады. Егер монитор бұрын басқа жіпке байланған болса, онда бұл құлыптау жеткіліксіз. JVM келесі құлыптау түріне ауысады - негізгі құлыптау. Ол салыстыру және ауыстыру (CAS) операцияларын пайдаланады. Сонымен қатар, Mark Word бағдарламасындағы тақырып енді Mark Word-тың өзін сақтамайды, бірақ оның қоймасына сілтеме + тег JVM негізгі құлыптауды қолданып жатқанымызды түсінуі үшін өзгертілді. Егер бірнеше ағындардың мониторы үшін келіспеушілік туындаса (біреуі мониторды басып алды, екіншісі монитордың босатылуын күтуде), онда Mark Word бағдарламасындағы тег өзгереді және Mark Word мониторға сілтеме ретінде сақтай бастайды. an object - JVM кейбір ішкі нысаны. JEP құжатында айтылғандай, бұл жағдайда осы нысанды сақтау үшін Native Heap жады аймағында бос орын қажет. Осы ішкі нысанның сақтау орнына сілтеме Mark Word нысанында орналасады. Осылайша, көріп отырғанымыздай, монитор шын мәнінде ортақ ресурстарға бірнеше ағындардың қол жеткізуін синхрондауды қамтамасыз ету механизмі болып табылады. JVM ауыстыратын осы механизмнің бірнеше іске асырылуы бар. Сондықтан, қарапайымдылық үшін, монитор туралы айтқанда, біз шын мәнінде құлыптар туралы айтып отырмыз. Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 7

Синхрондалған және құлыптау арқылы күтуде

Монитор түсінігі, біз бұрын көргеніміздей, «синхрондау блогы» (немесе оны сыни бөлім деп те атайды) түсінігімен тығыз байланысты. Мысал қарастырайық:
public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized (lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized (lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print("  " + i);
		}
		System.out.println(" ...");
	}
}
Мұнда негізгі ағын алдымен тапсырманы жаңа ағынға жібереді, содан кейін бірден құлыпты «ұстап алады» және онымен ұзақ әрекетті орындайды (8 секунд). Осы уақыт ішінде тапсырма оның орындалуы үшін блокқа кіре алмайды synchronized, өйткені құлып әлдеқашан бос. Егер жіп құлыпты ала алмаса, ол оны мониторда күтеді. Оны алған бойда ол орындауды жалғастырады. Монитордан жіп шыққанда, ол құлыпты босатады. JVisualVM-де ол келесідей болады: Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 8Көріп отырғаныңыздай, JVisualVM-дегі күй «Монитор» деп аталады, себебі ағын блокталған және мониторды ала алмайды. Сондай-ақ codeтағы ағынның күйін білуге ​​болады, бірақ бұл күйдің атауы JVisualVM шарттарымен сәйкес келмейді, бірақ олар ұқсас. Бұл жағдайда th1.getState()цикл BLOCKEDfor қайтарады , себебі Цикл жұмыс істеп тұрғанда, мониторды жіп басып алады , ал жіп бітеліп қалады және құлып қайтарылмайынша жұмысын жалғастыра алмайды. Синхрондау блоктарынан басқа, бүкіл әдісті синхрондауға болады. Мысалы, сыныптағы әдіс : lockmainth1HashTable
public synchronized int size() {
	return count;
}
Бір уақыт бірлігінде бұл әдіс тек бір ағынмен орындалады. Бірақ бізге құлып керек, солай ма? Иә маған керек. Нысандық әдістер жағдайында құлып болады this. Бұл тақырып бойынша қызықты пікірталас бар: " Синхрондалған блоктың орнына Синхрондалған әдісті пайдаланудың артықшылығы бар ма? ". Егер әдіс статикалық болса, онда құлып емес this(өйткені статикалық әдіс үшін болуы мүмкін емес this), сынып нысаны болады (Мысалы, Integer.class).

Мониторда күтіңіз және күтіңіз. Хабарлау және барлық әдістерді хабарлау

Thread мониторға қосылған басқа күту әдісіне ие. sleepжәне айырмашылығы join, оны жай ғана шақыруға болмайды. Ал оның аты wait. waitӘдіс мониторында күткіміз келетін нысанда орындалады . Мысал көрейік:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // task будет ждать, пока его не оповестят через lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // После оповещения нас мы будем ждать, пока сможем взять лок
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // Ждём и после этого забираем себе лок, оповещаем и отдаём лок
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
JVisualVM жүйесінде ол келесідей болады: Бұл қалай жұмыс істейтінін түсіну үшін әдістерге сілтеме жасайтынын есте Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 10сақтаңыз . Жіпке қатысты әдістердің болуы біртүрлі болып көрінеді . Бірақ жауап осында жатыр. Естеріңізде болса, Java тіліндегі әрбір нысанның тақырыбы бар. Тақырып әртүрлі қызмет ақпаратын, соның ішінде монитор туралы ақпаратты — құлыптау күйі туралы деректерді қамтиды. Естеріңізде болса, әрбір нысанда (яғни әрбір данада) ішкі құлып деп аталатын ішкі JVM нысанымен байланысы бар, оны монитор деп те атайды. Жоғарыдағы мысалда тапсырма мониторға байланыстырылған синхрондау блогына кіретінімізді сипаттайды . Егер бұл мониторда құлыпты алу мүмкін болса, онда . Бұл тапсырманы орындайтын ағын мониторды босатады , бірақ мониторда хабарландыруды күтіп тұрған ағындар кезегіне қосылады . Жіптердің бұл кезегі WAIT-SET деп аталады, ол мәнді дәлірек көрсетеді. Бұл кезектен гөрі жиынтық. Жіп тапсырма тапсырмасымен жаңа ағын жасайды, оны бастайды және 3 секунд күтеді. Бұл жоғары ықтималдықпен жаңа ағынға жіптен бұрын құлыпты ұстап алып , мониторда кезекке тұруға мүмкіндік береді. Осыдан кейін ағынның өзі синхрондау блогына кіреді және мониторда ағын туралы хабарламаны орындайды. Хабарландыру жіберілгеннен кейін ағын мониторды босатады және жаңа ағын (бұрын күтіп тұрған) монитордың шығарылуын күткеннен кейін орындалуын жалғастырады. Хабарландыруды тек бір ағынға ( ) немесе кезекте тұрған барлық ағындарға бірден жіберуге болады ( ). Толығырақ « Java тіліндегі notify() мен notifyAll() арасындағы айырмашылық » бөлімінен оқуға болады . Хабарландыру тәртібі JVM іске асыруға байланысты екенін ескеру маңызды. Толығырақ « Аштықты notify and notifyall көмегімен қалай шешуге болады? » бөлімінен оқи аласыз . Синхрондау нысанды көрсетпей-ақ орындалуы мүмкін. Бұл codeтың бөлек бөлімі емес, бүкіл әдіс синхрондалған кезде жасалуы мүмкін. Мысалы, статикалық әдістер үшін құлып сынып нысаны болады (арқылы алынған ): waitnotifyjava.lang.ObjectObjectlockwaitlocklockmainmainmainlockmainlocklocknotifynotifyAll.class
public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Құлыптарды пайдалану тұрғысынан екі әдіс бірдей. Егер әдіс статикалық болмаса, онда синхрондау токқа сәйкес орындалады instance, яғни сәйкес this. Айтпақшы, бұрын біз әдісті пайдалану арқылы getStateағынның күйін алуға болатынын айтқанбыз. Міне, монитор кезекке қойған ағын, егер әдіс waitкүту уақытының шегін көрсетсе, күй КҮТУ немесе TIMED_WAITING болады. Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 11

Жіптің өмірлік циклі

Байқағанымыздай, ағым өмір ағымында өз мәртебесін өзгертеді. Негізінде бұл өзгерістер жіптің өмірлік циклі болып табылады. Жіп жаңа ғана жасалғанда, оның ЖАҢА күйі болады. Бұл позицияда ол әлі басталған жоқ және Java Thread Scheduler жаңа ағын туралы әлі ештеңе білмейді. Ағынды жоспарлаушы ағын туралы білуі үшін thread.start(). Содан кейін ағын ЖҰМЫСТЫ күйге өтеді. Интернетте іске қосу және іске қосу күйлері бөлінген көптеген дұрыс емес схемалар бар. Бірақ бұл қате, өйткені... Java «іске қосуға дайын» ​​және «іске қосу» күйлерін ажыратпайды. Жіп тірі, бірақ белсенді емес кезде (Runnable емес), ол екі күйдің бірінде болады:
  • BLOCKED - қорғалған бөлімге кіруді күтеді, яғни. блокқа synchonized.
  • WAITING – шарт негізінде басқа ағынды күтеді. Шарт шын болса, ағынды жоспарлаушы ағынды бастайды.
Егер ағын уақыт бойынша күтіп тұрса, ол TIMED_WAITING күйінде. Егер ағын бұдан былай жұмыс істемесе (сәтті аяқталса немесе ерекше жағдай болса), ол ТОҚТАЛДЫ күйіне өтеді. Жіптің күйін (оның күйін) білу үшін әдіс қолданылады getState. isAliveСондай-ақ, ағындарда ағын аяқталмаса, ақиқат мәнін қайтаратын әдіс бар .

LockSupport және жіп тұрағы

Java 1.6 нұсқасынан бастап LockSupport деп аталатын қызықты механизм пайда болды . Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 12Бұл класс «рұқсат» немесе рұқсатты оны пайдаланатын әрбір ағынмен байланыстырады. Әдіс шақыруы parkқоңырау кезінде сол рұқсатты алатын рұқсат қол жетімді болса, дереу қайтарады. Әйтпесе бұғатталған. Әдіске қоңырау шалу, unparkол әлі қол жетімді болмаса, рұқсатты қол жетімді етеді. Тек 1 рұқсат бар. Java API интерфейсінде LockSupportбелгілі Semaphore. Қарапайым мысалды қарастырайық:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Просим разрешение и ждём, пока не получим его
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Бұл code мәңгі күтеді, себебі семафорда қазір 0 рұқсаты бар. Кодпен шақырылған кезде acquire(яғни, рұқсат сұрау), ағын рұқсат алғанша күтеді. Біз күтіп отырғандықтан, біз оны өңдеуге міндеттіміз InterruptedException. Бір қызығы, семафор бөлек ағын күйін жүзеге асырады. JVisualVM-ге қарасақ, біздің күйіміз Wait емес, Парк екенін көреміз. Сіз Java-ны ағынмен бұза алмайсыз: II бөлім - синхрондау - 13Басқа мысалды қарастырайық:
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            //Запаркуем текущий поток
            System.err.println("Will be Parked");
            LockSupport.park();
            // Как только нас распаркуют - начнём действовать
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());

        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
Жіп күйі КҮТІЛЕДІ болады, бірақ JVisualVM waitбастап synchronizedжәне parkбастап арасындағы айырмашылықты көрсетеді LockSupport. Бұл неге соншалықты маңызды LockSupport? Java API интерфейсіне қайта оралайық және Thread State WAITING параметрін қарастырайық . Көріп отырғаныңыздай, оған кірудің тек үш жолы бар. 2 жол - бұл waitжәне join. Ал үшінші - LockSupport. Java-дағы құлыптар бірдей принциптерге негізделген LockSupportжәне жоғары деңгейлі құралдарды білдіреді. Біреуін қолданып көрейік. Мысалы, мынаны қарастырайық ReentrantLock:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Алдыңғы мысалдардағыдай, мұнда бәрі қарапайым. lockбіреудің ресурсты шығаруын күтеді. mainJVisualVM-ге қарасақ, жаңа ағын ағын оған құлыпты бергенше тұрақталатынын көреміз . Құлыптар туралы толығырақ мына жерден оқи аласыз: " Java 8-де көп ағынды бағдарламалау. Екінші бөлім. Өзгермелі нысандарға қол жеткізуді синхрондау " және " Java Lock API. Теория және қолдану мысалы ." Құлыптарды іске асыруды жақсырақ түсіну үшін « Phaser Class » шолуында Phazer туралы оқу пайдалы . Әртүрлі синхронизаторлар туралы айтатын болсақ, сіз Habré туралы « Java.util.concurrent.* Синхронизаторларға сілтеме » мақаласын оқуыңыз керек .

Барлығы

Бұл шолуда біз Java тіліндегі ағындардың өзара әрекеттесуінің негізгі жолдарын қарастырдық. Қосымша материал: #Вячеслав
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION