JavaRush /Java блогу /Random-KY /Агымды башкаруу. туруксуз ачкыч жана yield() ыкмасы

Агымды башкаруу. туруксуз ачкыч жана yield() ыкмасы

Группада жарыяланган
Салам! Биз multithreading изилдөөнү улантып жатабыз жана бүгүн биз жаңы ачкыч сөз менен таанышабыз - volatile жана yield() ыкмасы. Келгиле, бул эмне экенин аныктап көрөлү :)

Ачкыч сөз туруксуз

Көп жиптүү тиркемелерди түзүүдө биз эки олуттуу көйгөйгө туш болушубуз мүмкүн. Биринчиден, көп агымдуу тиркеменин иштеши учурунда ар кандай жиптер өзгөрмөлөрдүн маанилерин кэштей алат (бул тууралуу биз "Учуучуну колдонуу" лекциясында кененирээк сүйлөшөбүз ). Бир жип өзгөрмөнүн маанисин өзгөртүшү мүмкүн, бирок экинчиси бул өзгөрүүнү көргөн жок, анткени ал өзгөрмөнүн өзүнүн кэштелген көчүрмөсү менен иштеген. Албетте, кесепеттери олуттуу болушу мүмкүн. Элестеткиле, бул жөн гана кандайдыр бир "өзгөрмө" эмес, мисалы, күтүлбөгөн жерден алдыга жана артка секире баштаган банк картаңыздын балансы :) Абдан жагымдуу эмес, туурабы? Экинчиден, Java-да атомдуктан башка бардык түрдөгү талаалардагы операцияларды окуу жана жазуу . longdouble атомизм деген эмне? Мисалы, эгер сиз бир жипте өзгөрмөнүн маанисин өзгөртсөңүз int, ал эми башка жипте бул өзгөрмөнүн маанисин окусаңыз, анын эски маанисин же жаңысын аласыз - өзгөргөндөн кийин пайда болгон жип 1. Ал жерде эч кандай "аралык параметрлер" пайда болбойт Мүмкүн. Бирок, бул longжана менен doubleиштебейт . Неге? Анткени бул кросс-платформа. Биринчи деңгээлде биз Java принциби "бир жолу жазылат, бардык жерде иштейт" деп айтканыбыз эсиңиздеби? Бул кросс-платформа. Башкача айтканда, Java колдонмосу такыр башка платформаларда иштейт. Мисалы, Windows операциялык системаларында, Linux же MacOSтун ар кандай versionларында жана бардык жерде бул тиркеме туруктуу иштейт. longжана double- Javaдагы эң "оор" примитивдер: алардын салмагы 64 бит. Ал эми кээ бир 32 биттик платформалар 64 биттик өзгөрмөлөрдү окуу жана жазуу атомдуулугун жөн эле ишке ашырbyte. Мындай өзгөрмөлөр эки операцияда окулат жана жазылат. Биринчиден, өзгөрмөгө биринчи 32 бит жазылат, андан кийин дагы 32. Демек, бул учурларда көйгөй келип чыгышы мүмкүн. Бир жип өзгөрмөгө 64 биттик маанини жазатХ, жана ал муну «эки кадам менен» жасайт. Ошол эле учурда, экинчи жип бул өзгөрмөнүн маанисин окууга аракет кылат жана биринчи 32 бит жазылган, бирок экинчилери али жазыла элек кезде, дал ортодо жасайт. Натыйжада, ал ортоңку, туура эмес маанини окуйт жана ката пайда болот. Мисалы, мындай платформада биз өзгөрмөгө санды жазууга аракет кылсак - 9223372036854775809 - ал 64 битти ээлейт. бинардык формада мындай болот: 10000000000000000000000000000000000000000000000000000000000000000000000000000001 Биринчи жип бул санды өзгөрмөгө жаза баштайт жана биринчи бит жаза баштайт: 100000000000000000000000 00000000 00000 анан экинчи 32: 000000000000000000000000000000001 Ал эми экинчи жип бул боштукка жана өзгөрмөнүн аралык маанисин окуу - 1000000000000000000000000000000000, буга чейин жазылган биринчи 32 бит. Ондук системада бул сан 2147483648ге барабар. Башкача айтканда, биз жөн гана 9223372036854775809 санын өзгөрмөгө жазгыбыз келди, бирок кээ бир платформаларда бул операция атомдук эмес болгондуктан, биз 2147483648 "сол" санын алдык. , бизге кереги жок, жок жерден. жана бул программанын иштешине кандай таасир этээри белгисиз. Экинчи жип акыры жазылганга чейин өзгөрмөнүн маанисин жөн гана окуйт, башкача айтканда, биринчи 32 битти көргөн, бирок экинчи 32 бит эмес. Бул көйгөйлөр, албетте, кечээ пайда болгон жок жана Javaда алар бир гана ачкыч сөздү колдонуу менен чечилет - volatile . Эгерде биз программабызда кандайдыр бир өзгөрмөлөрдү volatile деген сөз менен жарыяласак...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…бул дегенди билдирет:
  1. Ал ар дайым атомдук түрдө окулат жана жазылат. Ал 64-бит doubleже болсо да long.
  2. Java машинасы аны кэштебейт. Ошентип, 10 жип алардын жергorктүү көчүрмөлөрү менен иштеген жагдай жокко чыгарылат.
Бир сөз менен эки өтө олуттуу маселе ушундайча чечилет :)

yield() ыкмасы

Биз буга чейин класстын көптөгөн ыкмаларын карап чыктык Thread, бирок сиз үчүн жаңы боло турган бир маанилүү нерсе бар. Бул yield() ыкмасы . Англис тorнен "берүү" деп которулган. Бул ыкма так ушундай кылат! Агымды башкаруу.  Учма ачкыч сөз жана yield() ыкмасы - 2Биз жип боюнча түшүмдүүлүк ыкмасын чакырганда, ал чындыгында башка жиптерге мындай дейт: “Макул, балдар, мен өзгөчө шашкан жокмун, андыктан ар бириңиздерге CPU убактысын алуу маанилүү болсо, анда мен шашылыш эмес." Бул жерде анын кантип иштешинин жөнөкөй мисалы:
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();
   }
}
Биз ырааттуу түрдө үч жипти түзүп, ишке киргизебиз - Thread-0, Thread-1жана Thread-2. Thread-0биринчи башталып, дароо башкаларга жол берет. Андан кийин ал башталат Thread-1, ошондой эле жол берет. Андан кийин, ал башталат Thread-2, бул да төмөн. Бизде дагы жип жок, Thread-2акыркысы ордун бошоткондон кийин, жип пландоочусу: «Демек, жаңы жиптер жок, кезекте кимибиз бар? Буга чейин ким акыркы жолу өз ордун берген Thread-2? Менимче, бул болгон Thread-1? Макул, ошондой болсун". Thread-1аягына чейин өз ишин аткарат, андан кийин жип пландоочу координациялоону улантат: «Макул, Thread-1 аяктады. Бизде кезекте башка бирөө барбы?" Кезекте Thread-0 бар: ал Thread-1ге чейин дароо ордун таштап койду. Эми иш анын башына келип, аягына чейин аткарылып жатат. Андан кийин пландоочу жиптерди координациялоону аяктайт: “Макул, Thread-2, сиз башка жиптерге жол бердиңиз, алардын баары мурунтан эле иштеп калды. Сиз акыркы болуп жол бердиңиз, эми кезек сизде». Андан кийин Thread-2 аягына чейин иштейт. Консолдун чыгарылышы төмөнкүдөй болот: Thread-0 башкаларга жол берет Thread-1 башкаларга жол берет Thread-2 башкаларга жол берет Thread-1 аткарылып бүттү. Thread-0 аткарылып бүттү. Thread-2 аткарылып бүттү. Жип пландоочусу, албетте, жиптерди башка тартипте иштете алат (мисалы, 0-1-2 ордуна 2-1-0), бирок принцип бирдей.

Эрежелерден мурун болот

Бүгүн биз токтоло турган акыркы нерсе - бул " мурунку болот " принциптери. Белгилүү болгондой, Java-да жиптерге алардын тапшырмаларын аткаруу үчүн убакытты жана ресурстарды бөлүштүрүү жумуштарынын көбүн жип пландоочусу аткарат. Ошондой эле, сиз жиптер ыктыярдуу тартипте кантип аткарылганын бир нече жолу көрдүңүз жана көбүнчө аны алдын ала айтуу мүмкүн эмес. Жалпысынан, биз буга чейин жасаган "ырааттуу" программалоодон кийин, көп агым кокустук сыяктуу көрүнөт. Көрүнүп тургандай, көп агымдуу программанын жүрүшүн методдордун бүтүндөй комплекси аркылуу башкарууга болот. Мындан тышкары, Java көп агымында дагы бир "туруктуулук аралы" бар - " болот-мурда " деп аталган 4 эреже. Англис тorнен сөзмө-сөз бул "мурда болот" же "мурда болот" деп которулат. Бул эрежелердин маанисин түшүнүү үчүн абдан жөнөкөй. Элестеткиле, бизде эки жип бар - Aжана B. Бул жиптердин ар бири операцияларды аткара алат 1жана 2. Жана эрежелердин ар биринде биз “ А болот-Б алдында болотA ” деп айтсак, бул операцияга чейин жип тарабынан жасалган бардык өзгөртүүлөр жана бул операция алып келген өзгөрүүлөр операция жасалган учурда 1жипке көрүнүп турат жана операция жасалгандан кийин. Бул эрежелердин ар бири көп жиптүү программаны жазууда кээ бир окуялар башкалардан 100% мурда болоорун жана операция учурунда жип ар дайым операция учурунда жип жасаган өзгөрүүлөрдөн кабардар болушун камсыздайт. . Келгиле, аларды карап көрөлү. B2B2А1

1-эреже.

Мутексти чыгаруу башка жип бир эле мониторду алганга чейин ишке ашат . Ооба, бул жерде баары ачык көрүнөт. Эгерде an objectтин же класстын мутекси бир жип тарабынан алынса, мисалы, жип А, башка жип (жип B) аны бир эле учурда ала алbyte. Мутекс чыгарылганга чейин күтүшүңүз керек.

2-эреже.

Бул ыкмага Thread.start() чейин болот Thread.run() . Эч нерсе татаал эмес. Сиз буга чейин билесиз: методдун ичиндеги code ишке кириши үчүн run(), жиптеги ыкманы чакырышыңыз керек start(). Бул анын, методдун өзү эмес run()! Thread.start()Бул эреже аткарууга чейин коюлган бардык өзгөрмөлөрдүн маанилери аткарыла баштаган ыкманын ичинде көрүнөөрүн камсыздайт run().

Эреже 3.

Методдун аяктоосу методдон чыкканга run() чейин болотjoin() . Келгиле, эки агымыбызга кайтып келели - Ажана B. Биз ыкманы join()жип өз ишин аткаруудан мурун Bаягына чейин күтүшү керек деп атайбыз A. Бул А an objectинин ыкмасы run()сөзсүз аягына чейин иштейт дегенди билдирет. run()Ал эми жип ыкмасында болгон маалыматтардагы бардык өзгөрүүлөр, ал бүтүшүн күтүп , өзү иштей баштаганда Aжипте толугу менен көрүнүп калат .BA

4-эреже.

Туруктуу өзгөрмөгө жазуу бир эле өзгөрмөдөн окуудан мурун болот . туруксуз ачкыч колдонуу менен, биз, чынында, ар дайым учурдагы маанини алабыз. Жада калса longжана double, проблемалары мурда талкууланган. Сиз түшүнгөндөй, кээ бир жиптерде жасалган өзгөртүүлөр башка жиптерге дайыма эле көрүнбөйт. Бирок, албетте, мындай программалык жүрүм-турум бизге туура келбеген учурлар көп кездешет. Биз жиптеги өзгөрмөгө маани бердик дейли A:
int z;.

z= 555;
Эгерде биздин жип консолго Bөзгөрмөнүн маанисин басып чыгара турган болсо z, ал 0дү оңой басып чыгара алат, анткени ал ага дайындалган маанини билбейт. Ошентип, 4-эреже бизге кепилдик берет: эгер сиз өзгөрмөнү zтуруксуз деп жарыяласаңыз, анын бир жиптеги маанилериндеги өзгөрүүлөр ар дайым башка жипте көрүнүп турат. Мурунку codeго туруксуз деген сөздү кошсок...
volatile int z;.

z= 555;
B... агым консолго 0 чыгара турган кырдаал жок. Туруктуу өзгөрмөлөргө жазуу алардан окуудан мурун пайда болот.
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION