JavaRush /Java Blogu /Random-AZ /Axın idarə edilməsi. Uçucu açar söz və yield() metodu

Axın idarə edilməsi. Uçucu açar söz və yield() metodu

Qrupda dərc edilmişdir
Salam! Biz multithreading öyrənməyə davam edirik və bu gün biz yeni açar sözlə - uçucu və yield() metodu ilə tanış olacağıq. Bunun nə olduğunu anlayaq :)

Açar söz dəyişkəndir

Çox yivli proqramlar yaradarkən iki ciddi problemlə qarşılaşa bilərik. Birincisi, çox yivli bir tətbiqin işləməsi zamanı müxtəlif mövzular dəyişənlərin dəyərlərini keşləyə bilər (bu barədə daha çox "Uçucu istifadə" mühaziəsində danışacağıq ). Ola bilər ki, bir ip dəyişənin dəyərini dəyişdi, lakin ikincisi dəyişənin öz keşlənmiş nüsxəsi ilə işlədiyi üçün bu dəyişikliyi görmədi. Təbii ki, nəticələri ciddi ola bilər. Təsəvvür edin ki, bu, sadəcə, bir növ “dəyişən” deyil, məsələn, qəflətən təsadüfi irəli-geri atlamağa başlayan bank kartınızın balansıdır :) Çox xoş deyil, elə deyilmi? İkincisi, Java-da atomik olan istisna olmaqla, bütün növ sahələr üzrə əməliyyatları oxuyun və yazın . longdouble Atomluq nədir? Yaxşı, məsələn, bir mövzuda dəyişənin dəyərini dəyişdirsəniz intvə başqa bir mövzuda bu dəyişənin dəyərini oxusanız, ya onun köhnə dəyərini, ya da yenisini alacaqsınız - dəyişiklikdən sonra ortaya çıxan birini mövzu 1. Orada “ara seçimlər” görünməyəcək Bəlkə. Ancaq bu longvə ilə doubleişləmir . Niyə? Çünki o, çarpaz platformadır. Yadınızdadırmı, ilk səviyyələrdə Java prinsipinin “bir dəfə yazıldığını, hər yerdə işlədiyini” necə demişdik? Bu çarpaz platformadır. Yəni Java proqramı tamamilə fərqli platformalarda işləyir. Məsələn, Windows əməliyyat sistemlərində, Linux və ya MacOS-un müxtəlif versiyalarında və hər yerdə bu proqram sabit işləyəcək. longdouble- Java-da ən "ağır" primitivlər: onların çəkisi 64 bitdir. Və bəzi 32-bit platformalar sadəcə olaraq 64-bit dəyişənlərin oxunması və yazılmasının atomikliyini həyata keçirmir. Belə dəyişənlər iki əməliyyatda oxunur və yazılır. Əvvəlcə dəyişənə ilk 32 bit, sonra başqa 32 bit yazılır. Müvafiq olaraq, bu hallarda problem yarana bilər. Bir başlıq dəyişənə 64 bit dəyər yazırХvə o bunu “iki addımda” edir. Eyni zamanda, ikinci ip bu dəyişənin qiymətini oxumağa çalışır və bunu ilk 32 bit artıq yazılmış, lakin ikincilər hələ yazılmamış ortada edir. Nəticədə, aralıq, səhv dəyəri oxuyur və xəta baş verir. Məsələn, belə bir platformada dəyişənə - 9223372036854775809-a rəqəm yazmağa çalışsaq, o, 64 bit tutacaq. Binar formada bu belə görünəcək: 100000000000000000000000000000000000000000000000000000000000000000000000000000001 Birinci mövzu bu rəqəmi dəyişənə yazmağa başlayacaq və ilk olaraq ilk biti yazacaq: 100000000000000132 00000000 00000 və sonra ikinci 32: 000000000000000000000000000000001 Və ikinci bir ip bu boşluğa girə bilər və dəyişənin aralıq qiymətini oxuyun - 1000000000000000000000000000000000, artıq yazılmış ilk 32 bit. Onluq sistemdə bu rəqəm 2147483648-ə bərabərdir.Yəni biz sadəcə olaraq 9223372036854775809 rəqəmini dəyişənə yazmaq istədik, lakin bəzi platformalarda bu əməliyyat atomik olmadığı üçün 2147483648 “sol” nömrəsini aldıq. , bizə lazım olmayan, heç bir yerdən. və proqramın işinə necə təsir edəcəyi bilinmir. İkinci ip nəhayət yazılmamışdan əvvəl dəyişənin dəyərini sadəcə oxudu, yəni ilk 32 biti gördü, ikinci 32 biti yox. Bu problemlər, əlbəttə ki, dünən yaranmayıb və Java-da onlar yalnız bir açar sözdən istifadə etməklə həll olunur - uçucu . Proqramımızda hansısa dəyişəni uçucu sözü ilə elan etsək...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
...bu o deməkdir ki:
  1. Həmişə atomik şəkildə oxunacaq və yazılacaqdır. double64 bit və ya olsa belə long.
  2. Java maşını onu keş etməyəcək. Beləliklə, 10 mövzunun yerli nüsxələri ilə işlədiyi vəziyyət istisna olunur.
İki çox ciddi problem bir sözlə belə həll olunur :)

yield() metodu

Biz artıq sinifin bir çox metodlarına baxmışıq Thread, lakin sizin üçün yeni olacaq bir vacib metod var. Bu verim() metodudur . İngilis dilindən "vermək" kimi tərcümə olunur. Və metod məhz bunu edir! Axın idarə edilməsi.  Uçucu açar söz və yield() metodu - 2Biz iplikdə gəlir metodunu çağırdıqda, o, əslində digər mövzulara deyir: “Yaxşı, uşaqlar, mən xüsusi tələsmirəm, ona görə də hər hansı biriniz üçün CPU vaxtını əldə etmək vacibdirsə, onu götürün, mən təcili deyil." Bunun necə işlədiyinə dair sadə bir nümunə:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + "give way to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
Ardıcıl olaraq üç mövzu yaradır və işə salırıq - Thread-0, Thread-1Thread-2. Thread-0birinci başlayır və dərhal yerini başqalarına verir. Ondan sonra başlayır Thread-1, həm də yol verir. Bundan sonra başlayır Thread-2, bu da aşağıdır. Artıq mövzumuz yoxdur və Thread-2sonuncu öz yerini tərk etdikdən sonra mövzu planlayıcısı baxır: “Beləliklə, daha yeni mövzular yoxdur, növbəmizdə kim var? Daha əvvəl yerini ən son kim təslim etdi Thread-2? Məncə belə oldu Thread-1? Yaxşı, qoy bunu etsin." Thread-1sona qədər öz işini görür, bundan sonra mövzu planlayıcısı koordinasiya etməyə davam edir: “Yaxşı, Thread-1 tamamlandı. Bizim növbəmizdə başqa kimsə varmı?" Növbədə Thread-0 var: o, Thread-1-dən dərhal əvvəl yerini verdi. İndi məsələ onun başına gəlib və o, sona qədər həyata keçirilir. Bundan sonra planlaşdırıcı mövzuları əlaqələndirməyi bitirir: “Yaxşı, Thread-2, siz başqa mövzulara yol verdiniz, onların hamısı artıq işləmişdir. Sonuncu yol verən sən idin, indi növbə sənindir”. Bundan sonra Thread-2 tamamlanana qədər çalışır. Konsol çıxışı belə görünəcək: Thread-0 başqalarına yol verir Thread-1 başqalarına yol verir Thread-2 başqalarına verir Thread-1 icrasını bitirdi. Thread-0 icrasını bitirdi. Thread-2 icrasını bitirdi. Mövzu planlayıcısı, əlbəttə ki, ipləri fərqli ardıcıllıqla işlədə bilər (məsələn, 0-1-2 əvəzinə 2-1-0), lakin prinsip eynidir.

Qaydalardan əvvəl olur

Bu gün toxunacağımız son şey " əvvəl olur " prinsipləridir. Artıq bildiyiniz kimi, Java-da tapşırıqları yerinə yetirmək üçün mövzulara vaxt və resursların ayrılması işinin çoxu mövzu planlaşdırıcısı tərəfindən həyata keçirilir. Ayrıca, iplərin ixtiyari qaydada necə icra olunduğunu bir dəfədən çox görmüsünüz və çox vaxt bunu proqnozlaşdırmaq mümkün deyil. Və ümumiyyətlə, əvvəllər etdiyimiz "ardıcıl" proqramlaşdırmadan sonra çox iş parçacığı təsadüfi bir şey kimi görünür. Artıq gördüyünüz kimi, çoxillik proqramın gedişatını bütün metodlar dəstindən istifadə etməklə idarə etmək olar. Bununla yanaşı, Java multithreading-də başqa bir "sabitlik adası" var - " əvvəl baş verir " adlı 4 qayda. İngilis dilindən hərfi mənada bu "əvvəl baş verir" və ya "əvvəl baş verir" kimi tərcümə olunur. Bu qaydaların mənasını başa düşmək olduqca sadədir. Təsəvvür edin ki, iki mövzumuz var - AB. Bu iplərin hər biri əməliyyatları yerinə yetirə bilər 12. Və qaydaların hər birində “ A baş verir - B-dən əvvəl ” dedikdə , bu o deməkdir ki A, əməliyyatdan əvvəl ip tərəfindən edilən bütün dəyişikliklər və bu əməliyyatın gətirdiyi dəyişikliklər əməliyyat yerinə yetirilən zaman 1ipdə görünür və əməliyyatdan sonra. Bu qaydaların hər biri çoxillik proqram yazarkən bəzi hadisələrin digərlərindən əvvəl 100% baş verməsini və əməliyyat zamanı ipin əməliyyat zamanı ipin etdiyi dəyişikliklərdən həmişə xəbərdar olmasını təmin edir. . Gəlin onlara baxaq. B2B2А1

Qayda 1.

Muteksin buraxılması başqa bir mövzu eyni monitoru əldə etməzdən əvvəl baş verir . Bax, burada hər şey aydın görünür. Əgər obyektin və ya sinfin mutexi bir iplə, məsələn, bir iplə əldə edilirsə А, başqa bir ip (thread B) onu eyni vaxtda əldə edə bilməz. Muteks buraxılana qədər gözləmək lazımdır.

Qayda 2.

Bu üsuldan Thread.start() əvvəl olur Thread.run() . Heç bir şey mürəkkəb deyil. Artıq bilirsiniz: metodun içindəki kodun icra etməyə başlaması üçün run()iplikdəki metodu çağırmalısınız start(). Bu onundur, metodun özü deyil run()! Bu qayda Thread.start()icradan əvvəl təyin edilmiş bütün dəyişənlərin dəyərlərinin icra etməyə başlayan metodun içərisində görünməsini təmin edir run().

Qayda 3.

Metodun tamamlanması metoddan çıxmazdan run() əvvəl baş verirjoin() . Gəlin iki axınımıza qayıdaq - АB. Biz metodu join()elə çağırırıq ki, ip öz işini görməzdən əvvəl Btamamlanana qədər gözləməlidir . ABu o deməkdir ki, A obyektinin metodu run()mütləq sona qədər işləyəcək. run()Və iplik metodunda baş verən məlumatlarda baş verən bütün dəyişikliklər, tamamlanmasını gözlədikdə və özü işləməyə başlayanda Aipdə tamamilə görünəcəkdir .BA

Qayda 4.

Dəyişən dəyişənə yazmaq eyni dəyişəndən oxumadan əvvəl baş verir . Dəyişən açar sözdən istifadə etməklə, biz, əslində, həmişə cari dəyəri alacağıq. Problemləri əvvəllər müzakirə edilən longvə vəziyyətində belə . doubleArtıq başa düşdüyünüz kimi, bəzi mövzularda edilən dəyişikliklər həmişə digər mövzulara görünmür. Ancaq əlbəttə ki, çox vaxt belə proqram davranışının bizə uyğun olmadığı vəziyyətlər olur. Deyək ki, mövzuda dəyişənə dəyər təyin etdik A:
int z;.

z= 555;
Əgər ipimiz Bdəyişənin dəyərini zkonsola çap etsəydi, ona təyin edilmiş dəyəri bilmədiyi üçün 0-ı asanlıqla çap edə bilərdi. Beləliklə, 4-cü Qayda bizə zəmanət verir: dəyişəni dəyişkən elan etsəniz z, onun bir başlıqdakı dəyərlərinə edilən dəyişikliklər həmişə digər mövzuda görünəcək. Əvvəlki koda uçucu söz əlavə etsək...
volatile int z;.

z= 555;
B...axın konsola 0 çıxaracağı vəziyyət istisna edilir. Dəyişən dəyişənlərə yazmaq onlardan oxumadan əvvəl baş verir.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION