JavaRush /Java blogi /Random-UZ /Oqimni boshqarish. O'zgaruvchan kalit so'z va yield() usu...

Oqimni boshqarish. O'zgaruvchan kalit so'z va yield() usuli

Guruhda nashr etilgan
Salom! Biz multithreadingni o'rganishni davom ettirmoqdamiz va bugun biz yangi kalit so'z - volatile va yield() usuli bilan tanishamiz. Keling, nima ekanligini aniqlaylik :)

Kalit so'z o'zgaruvchan

Ko'p oqimli ilovalarni yaratishda biz ikkita jiddiy muammoga duch kelishimiz mumkin. Birinchidan, ko'p tarmoqli dasturning ishlashi paytida turli xil oqimlar o'zgaruvchilar qiymatlarini keshlashi mumkin (bu haqda ko'proq ma'ruzada gaplashamiz "O'zgaruvchan foydalanish" ). Ehtimol, bitta ip o'zgaruvchining qiymatini o'zgartirgan bo'lishi mumkin, lekin ikkinchisi bu o'zgarishni ko'rmadi, chunki u o'zgaruvchining keshlangan nusxasi bilan ishlagan. Tabiiyki, oqibatlari jiddiy bo'lishi mumkin. Tasavvur qiling-a, bu shunchaki "o'zgaruvchan" emas, balki, masalan, to'satdan oldinga va orqaga sakrashni boshlagan bank kartangiz balansi :) Juda yoqimli emas, to'g'rimi? Ikkinchidan, Java-da atomdan tashqari barcha turdagi maydonlarda o'qish va yozish operatsiyalari . longdouble Atomlik nima? Xo'sh, masalan, agar siz bitta o'zgaruvchining qiymatini o'zgartirsangiz intva boshqa ipda ushbu o'zgaruvchining qiymatini o'qisangiz, siz uning eski qiymatini yoki yangisini olasiz - o'zgartirilgandan keyin paydo bo'lganini mavzu 1. U erda "oraliq variantlar" ko'rinmaydi Balki. Biroq, bu longva bilan doubleishlamaydi . Nega? Chunki u platformalararo. Birinchi darajalarda Java printsipi "bir marta yoziladi, hamma joyda ishlaydi" deb aytganimizni eslaysizmi? Bu kross-platforma. Ya'ni, Java dasturi butunlay boshqa platformalarda ishlaydi. Masalan, Windows operatsion tizimlarida, Linux yoki MacOS ning turli versiyalarida va hamma joyda bu dastur barqaror ishlaydi. longva double- Java-dagi eng "og'ir" primitivlar: ularning og'irligi 64 bit. Va ba'zi 32-bitli platformalar 64-bitli o'zgaruvchilarni o'qish va yozishning atomikligini oddiygina amalga oshirmaydi. Bunday o'zgaruvchilar ikki amalda o'qiladi va yoziladi. Birinchidan, birinchi 32 bit o'zgaruvchiga yoziladi, keyin yana 32. Shunga ko'ra, bu holatlarda muammo paydo bo'lishi mumkin. Bitta ip o'zgaruvchiga 64 bitli qiymat yozadiХ, va u buni "ikki bosqichda" qiladi. Shu bilan birga, ikkinchi ip bu o'zgaruvchining qiymatini o'qishga harakat qiladi va buni birinchi 32 bit allaqachon yozilgan, lekin ikkinchisi hali yozilmagan bo'lsa, o'rtada bajaradi. Natijada, u oraliq, noto'g'ri qiymatni o'qiydi va xatolik yuzaga keladi. Misol uchun, agar bunday platformada biz o'zgaruvchiga raqam yozishga harakat qilsak - 9223372036854775809 - u 64 bitni egallaydi. Ikkilik shaklda u quyidagicha ko'rinadi: 1000000000000000000000000000000000000000000000000000000000000000001 Birinchi ip bu raqamni o'zgaruvchiga yozishni boshlaydi va birinchi navbatda birinchi bitni yozadi:100000000000000000 00000000 00000 va keyin ikkinchi 32: 000000000000000000000000000000001 Va ikkinchi ip bu bo'shliqqa kirishi mumkin va o'zgaruvchining oraliq qiymatini o'qing - 1000000000000000000000000000000000, allaqachon yozilgan birinchi 32 bit. O'nli kasr tizimida bu raqam 2147483648 ga teng. Ya'ni biz shunchaki 9223372036854775809 sonini o'zgaruvchiga yozmoqchi bo'ldik, lekin ba'zi platformalarda bu operatsiya atomik emasligi sababli biz "chap" raqamini oldik 2147483648 , bizga kerak bo'lmagan joydan. va bu dasturning ishlashiga qanday ta'sir qilishi noma'lum. Ikkinchi ip shunchaki yozilgunga qadar o'zgaruvchining qiymatini o'qidi, ya'ni u birinchi 32 bitni ko'rdi, lekin ikkinchi 32 bitni ko'rmadi. Bu muammolar, albatta, kecha paydo bo'lmadi va Java-da ular faqat bitta kalit so'z yordamida hal qilinadi - volatile . Agar dasturimizda qandaydir o'zgaruvchini volatile so'zi bilan e'lon qilsak...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
...bu shuni anglatadiki:
  1. U har doim atomik tarzda o'qiladi va yoziladi. double64-bit yoki bo'lsa ham long.
  2. Java mashinasi uni keshlashtirmaydi. Shunday qilib, 10 ta ip o'zlarining mahalliy nusxalari bilan ishlaydigan vaziyat bundan mustasno.
Bir so'z bilan ikkita o'ta jiddiy muammo shunday hal qilinadi :)

yield() usuli

Biz allaqachon sinfning ko'plab usullarini ko'rib chiqdik Thread, lekin siz uchun yangi bo'lgan bitta muhim narsa bor. Bu yield() usuli . Ingliz tilidan “give in” deb tarjima qilingan. Va bu usul aynan shunday qiladi! Oqimni boshqarish.  O'zgaruvchan kalit so'z va yield() usuli - 2Biz ipda rentabellik usulini chaqirganimizda, u aslida boshqa mavzularga aytadi: "Yaxshi, bolalar, men shoshilmayapman, shuning uchun sizlardan biringiz uchun protsessor vaqtini olish muhim bo'lsa, uni oling, men shoshilinch emas." Bu qanday ishlashiga oddiy misol:
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();
   }
}
Biz ketma-ket uchta ipni yaratamiz va ishga tushiramiz - Thread-0, Thread-1va Thread-2. Thread-0birinchi bo'lib boshlanadi va darhol boshqalarga yo'l beradi. Undan keyin u boshlanadi Thread-1, shuningdek, yo'l beradi. Shundan so'ng, u boshlanadi Thread-2, bu ham pastroq. Bizda boshqa mavzular yo'q va Thread-2oxirgisi o'z o'rnini bo'shatgandan so'ng, ip rejalashtiruvchisi qaraydi: "Demak, endi yangi mavzular yo'q, bizda navbatda kim bor? Ilgari o'z o'rnini oxirgi marta kim berdi Thread-2? Menimcha, shunday edi Thread-1? Mayli, shunday bo'lsin." Thread-1o'z ishini oxirigacha bajaradi, shundan so'ng ipni rejalashtiruvchi muvofiqlashtirishni davom ettiradi: "Yaxshi, Thread-1 tugallandi. Bizda yana kimdir bormi?" Navbatda Thread-0 mavjud: u Thread-1 dan oldin darhol o'z o'rnini tashlab ketdi. Endi ish uning oldiga keldi va u oxirigacha bajarilmoqda. Shundan so'ng, rejalashtiruvchi iplarni muvofiqlashtirishni tugatadi: "Yaxshi, Thread-2, siz boshqa mavzularga yo'l berdingiz, ularning barchasi allaqachon ishlagan. Siz oxirgi bo'lib yo'l berdingiz, endi sizning navbatingiz." Shundan so'ng, Thread-2 tugallanadi. Konsol chiqishi quyidagicha bo'ladi: Thread-0 boshqalarga o'z o'rnini beradi Thread-1 boshqalarga o'z o'rnini beradi Thread-2 boshqalarga o'z o'rnini beradi Thread-1 bajarilishini tugatdi. Thread-0 bajarilishni tugatdi. Thread-2 bajarilishini tugatdi. Mavzuni rejalashtiruvchi, albatta, iplarni boshqa tartibda ishga tushirishi mumkin (masalan, 0-1-2 o'rniga 2-1-0), lekin printsip bir xil.

Qoidalardan oldin sodir bo'ladi

Bugun biz to'xtaladigan oxirgi narsa - bu " oldin sodir bo'ladi " tamoyillari. Ma'lumki, Java-da, ularning vazifalarini bajarish uchun mavzularga vaqt va resurslarni taqsimlash bo'yicha ishlarning aksariyati mavzuni rejalashtiruvchi tomonidan amalga oshiriladi. Shuningdek, siz iplar qanday qilib o'zboshimchalik bilan bajarilishini bir necha bor ko'rgansiz va ko'pincha buni oldindan aytib bo'lmaydi. Va umuman olganda, biz ilgari qilgan "ketma-ket" dasturlashdan so'ng, multithreading tasodifiy narsaga o'xshaydi. Ko'rib turganingizdek, ko'p bosqichli dasturning borishini butun usullar to'plami yordamida boshqarish mumkin. Bunga qo'shimcha ravishda, Java multithreading-da yana bir "barqarorlik oroli" mavjud - " oldindan sodir bo'ladi " deb nomlangan 4 ta qoida. Ingliz tilidan so'zma-so'z bu "oldin sodir bo'ladi" yoki "oldin sodir bo'ladi" deb tarjima qilinadi. Ushbu qoidalarning ma'nosini tushunish juda oddiy. Tasavvur qiling-a, bizda ikkita ip bor - Ava B. Ushbu iplarning har biri operatsiyalarni bajarishi mumkin 1va 2. Va har bir qoidada biz " A sodir bo'ladi - B dan oldinA " desak, bu operatsiyadan oldin ip tomonidan kiritilgan barcha o'zgarishlar va bu operatsiyaga olib keladigan o'zgarishlar operatsiyani bajarish vaqtida 1ipga ko'rinadi va operatsiyadan keyin. Ushbu qoidalarning har biri ko'p bosqichli dasturni yozishda ba'zi voqealar boshqalardan oldin yuz foiz sodir bo'lishini va operatsiya vaqtida ip har doim operatsiya davomida amalga oshirilgan o'zgarishlardan xabardor bo'lishini ta'minlaydi. . Keling, ularga qaraylik. B2B2А1

1-qoida.

Muteksni chiqarish boshqa bir ish zarrachasi bir xil monitorni olishdan oldin sodir bo'ladi . Xo'sh, bu erda hamma narsa aniq ko'rinadi. Agar ob'ekt yoki sinfning mutexi bitta ip tomonidan olingan bo'lsa, masalan, ip А, boshqa ip (thread B) uni bir vaqtning o'zida ololmaydi. Muteks chiqarilguncha kutishingiz kerak.

2-qoida.

Bu usuldan Thread.start() oldin sodir bo'ladi Thread.run() . Hech narsa murakkab emas. Siz allaqachon bilasiz: usul ichidagi kodni bajarishni boshlash uchun run()siz ipdagi usulni chaqirishingiz kerak start(). Bu usulning o'zi emas, balki uniki run()! Ushbu qoida Thread.start()bajarishdan oldin o'rnatilgan barcha o'zgaruvchilar qiymatlari bajara boshlagan usul ichida ko'rinishini ta'minlaydi run().

3-qoida.

Usul tugallanishi usuldan chiqishdan run() oldin sodir bo'ladijoin() . Keling, ikkita oqimimizga qaytaylik - Аva B. Biz usulni join()shunday chaqiramizki, ip o'z ishini bajarishdan oldin Btugashini kutishi kerak . ABu shuni anglatadiki, A ob'ektining usuli, run()albatta, oxirigacha ishlaydi. run()Va ip usulida sodir bo'lgan ma'lumotlardagi barcha o'zgarishlar, u tugashni kutayotganda va o'zi ishlay boshlaganda Aipda to'liq ko'rinadi .BA

4-qoida.

O'zgaruvchan o'zgaruvchiga yozish bir xil o'zgaruvchidan o'qishdan oldin sodir bo'ladi . O'zgaruvchan kalit so'zdan foydalanib, biz, aslida, har doim joriy qiymatni olamiz. longHatto va holatlarida ham double, muammolari avvalroq muhokama qilingan. Siz allaqachon tushunganingizdek, ba'zi mavzularda kiritilgan o'zgarishlar har doim ham boshqa mavzularga ko'rinmaydi. Lekin, albatta, ko'pincha bunday dastur xatti-harakatlari bizga mos kelmaydigan holatlar mavjud. Aytaylik, biz ipdagi o'zgaruvchiga qiymat berdik A:
int z;.

z= 555;
Agar bizning ipimiz konsolga Bo'zgaruvchining qiymatini chop etsa z, u 0 ni osongina bosib chiqarishi mumkin edi, chunki u unga tayinlangan qiymat haqida bilmaydi. Shunday qilib, 4-qoida bizga kafolat beradi: agar siz o'zgaruvchini zo'zgaruvchan deb e'lon qilsangiz, uning bir ipdagi qiymatlariga kiritilgan o'zgarishlar har doim boshqa ipda ko'rinadi. Agar oldingi kodga volatile so'zini qo'shsak ...
volatile int z;.

z= 555;
B... oqim 0 ni konsolga chiqaradigan vaziyat chiqarib tashlandi. O'zgaruvchan o'zgaruvchilarga yozish ulardan o'qishdan oldin sodir bo'ladi.
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION