CodeGym /Blog Java /rawak /Menguruskan benang. Kata kunci yang tidak menentu dan kae...
John Squirrels
Tahap
San Francisco

Menguruskan benang. Kata kunci yang tidak menentu dan kaedah hasil().

Diterbitkan dalam kumpulan
Hai! Kami meneruskan kajian kami tentang multithreading. Hari ini kita akan mengenali volatilekata kunci dan yield()kaedahnya. Jom terjun :)

Kata kunci yang tidak menentu

Apabila mencipta aplikasi berbilang benang, kita boleh menghadapi dua masalah serius. Pertama, apabila aplikasi berbilang benang sedang berjalan, benang yang berbeza boleh menyimpan nilai pembolehubah (kita telah membincangkan perkara ini dalam pelajaran bertajuk 'Menggunakan volatile' ). Anda boleh mengalami situasi di mana satu utas mengubah nilai pembolehubah, tetapi utas kedua tidak melihat perubahan itu, kerana ia berfungsi dengan salinan pembolehubahnya yang dicache. Sememangnya, akibatnya boleh menjadi serius. Katakan bahawa ia bukan sebarang pembolehubah lama tetapi baki akaun bank anda, yang tiba-tiba mula melonjak naik dan turun secara rawak :) Itu tidak terdengar seperti menyeronokkan, bukan? Kedua, dalam Java, operasi untuk membaca dan menulis semua jenis primitif,longdouble, adalah atom. Contohnya, jika anda menukar nilai pembolehubah intpada satu utas, dan pada utas lain anda membaca nilai pembolehubah itu, anda sama ada akan mendapat nilai lama atau yang baharu, iaitu nilai yang terhasil daripada perubahan itu. dalam benang 1. Tiada 'nilai perantaraan'. Walau bagaimanapun, ini tidak berfungsi dengan longs dan doubles. kenapa? Kerana sokongan merentas platform. Ingat pada peringkat permulaan yang kami katakan bahawa prinsip panduan Java ialah 'tulis sekali, jalankan di mana-mana'? Ini bermakna sokongan merentas platform. Dengan kata lain, aplikasi Java berjalan pada semua jenis platform yang berbeza. Contohnya, pada sistem pengendalian Windows, versi Linux atau MacOS yang berbeza. Ia akan berjalan tanpa halangan pada mereka semua. Dengan berat dalam 64 bit,longdoubleadalah primitif 'paling berat' di Jawa. Dan platform 32-bit tertentu hanya tidak melaksanakan pembacaan dan penulisan atom pembolehubah 64-bit. Pembolehubah sedemikian dibaca dan ditulis dalam dua operasi. Pertama, 32 bit pertama ditulis kepada pembolehubah, dan kemudian 32 bit lagi ditulis. Akibatnya, masalah mungkin timbul. Satu benang menulis beberapa nilai 64-bit kepada Xpembolehubah dan melakukannya dalam dua operasi. Pada masa yang sama, benang kedua cuba membaca nilai pembolehubah dan melakukannya di antara kedua-dua operasi tersebut — apabila 32 bit pertama telah ditulis, tetapi 32 bit kedua tidak. Akibatnya, ia membaca nilai pertengahan, salah, dan kami mempunyai pepijat. Sebagai contoh, jika pada platform sedemikian kami cuba menulis nombor ke 9223372036854775809 kepada pembolehubah, ia akan menduduki 64 bit. Dalam bentuk binari, ia kelihatan seperti ini: 10000000000000000000000000000000000000000000000000000000001 Benang pertama mula menulis nombor kepada pembolehubah. Pada mulanya, ia menulis 32 bit pertama (100000000000000000000000000000) dan kemudian 32 bit kedua (000000000000000000000000000001) Dan benang kedua boleh tersepit di antara operasi ini, membaca nilai perantaraan pembolehubah (100000000000000000000000000000000), yang merupakan 32 bit pertama yang telah ditulis. Dalam sistem perpuluhan, nombor ini ialah 2,147,483,648. Dalam erti kata lain, kami hanya mahu menulis nombor 9223372036854775809 kepada pembolehubah, tetapi disebabkan oleh fakta bahawa operasi ini bukan atom pada beberapa platform, kami mempunyai nombor jahat 2,147,483,648, yang datang entah dari mana dan akan mempunyai kesan yang tidak diketahui. program. Benang kedua hanya membaca nilai pembolehubah sebelum ia selesai ditulis, iaitu benang melihat 32 bit pertama, tetapi bukan 32 bit kedua. Sudah tentu, masalah ini tidak timbul semalam. Java menyelesaikannya dengan satu kata kunci: volatile. Jika kita menggunakanvolatilekata kunci apabila mengisytiharkan beberapa pembolehubah dalam program kami…

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…maksudnya:
  1. Ia akan sentiasa dibaca dan ditulis secara atom. Walaupun ia adalah 64-bit doubleatau long.
  2. Mesin Java tidak akan menyimpannya. Oleh itu, anda tidak akan mengalami situasi di mana 10 utas berfungsi dengan salinan tempatan mereka sendiri.
Oleh itu, dua masalah yang sangat serius diselesaikan dengan hanya satu perkataan :)

Kaedah hasil().

Kami telah menyemak banyak Threadkaedah kelas, tetapi terdapat kaedah penting yang akan menjadi baharu kepada anda. Ia adalah yield()kaedahnya . Dan ia melakukan apa yang tersirat dari namanya! Menguruskan benang.  Kata kunci yang tidak menentu dan kaedah yield() - 2Apabila kita memanggil yieldkaedah pada benang, ia sebenarnya bercakap dengan benang lain: 'Hei, kawan-kawan. Saya tidak terlalu tergesa-gesa untuk pergi ke mana-mana, jadi jika penting bagi mana-mana daripada anda untuk mendapatkan masa pemproses, ambil masa — saya boleh menunggu'. Berikut ialah contoh mudah tentang cara ini berfungsi:

public class ThreadExample extends Thread {

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

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place 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();
   }
}
Kami membuat dan memulakan tiga urutan secara berurutan: Thread-0, Thread-1, dan Thread-2. Thread-0bermula dahulu dan serta-merta menghasilkan kepada yang lain. Kemudian Thread-1dimulakan dan juga menghasilkan. Kemudian Thread-2dimulakan, yang juga menghasilkan. Kami tidak mempunyai apa-apa benang lagi dan selepas Thread-2menghasilkan tempat terakhir, penjadual benang berkata, 'Hmm, tiada lagi benang baharu. Siapa yang kita ada dalam barisan? Siapa yang memberikan tempatnya sebelum ini Thread-2? Nampaknya memang begitu Thread-1. Okay, itu bermakna kita akan biarkan ia berjalan'. Thread-1menyelesaikan kerjanya dan kemudian penjadual benang meneruskan penyelarasannya: 'Baiklah, Thread-1selesai. Adakah kita mempunyai orang lain dalam barisan?'. Thread-0 berada dalam baris gilir: ia memberikan tempatnya sebelum iniThread-1. Ia kini mendapat gilirannya dan berjalan hingga siap. Kemudian penjadual selesai menyelaraskan urutan: 'Baiklah, Thread-2, anda telah menyerah kepada urutan lain, dan semuanya telah selesai sekarang. Anda adalah yang terakhir mengalah, jadi kini giliran anda'. Kemudian Thread-2berjalan hingga selesai. Output konsol akan kelihatan seperti ini: Thread-0 menghasilkan tempatnya kepada orang lain Thread-1 menghasilkan tempatnya kepada orang lain Thread-2 menghasilkan tempatnya kepada orang lain Thread-1 telah selesai dilaksanakan. Thread-0 telah selesai dilaksanakan. Thread-2 telah selesai dilaksanakan. Sudah tentu, penjadual benang mungkin memulakan benang dalam susunan yang berbeza (contohnya, 2-1-0 dan bukannya 0-1-2), tetapi prinsipnya tetap sama.

Berlaku-sebelum peraturan

Perkara terakhir yang akan kita sentuh hari ini ialah konsep ' berlaku sebelum '. Seperti yang anda sedia maklum, dalam Java penjadual benang melaksanakan sebahagian besar kerja yang terlibat dalam memperuntukkan masa dan sumber kepada benang untuk melaksanakan tugas mereka. Anda juga telah berulang kali melihat cara urutan dilaksanakan dalam susunan rawak yang biasanya mustahil untuk diramalkan. Dan secara umum, selepas pengaturcaraan 'berurutan' yang kami lakukan sebelum ini, pengaturcaraan berbilang benang kelihatan seperti sesuatu yang rawak. Anda sudah mula percaya bahawa anda boleh menggunakan pelbagai kaedah untuk mengawal aliran program berbilang benang. Tetapi multithreading di Jawa mempunyai satu tiang lagi — peraturan 4 ' berlaku-sebelum '. Memahami peraturan ini agak mudah. Bayangkan kita mempunyai dua utas — AdanB. Setiap utas ini boleh melakukan operasi 1dan 2. Dalam setiap peraturan, apabila kami menyebut ' A berlaku-sebelum B ', kami maksudkan bahawa semua perubahan yang dibuat oleh benang Asebelum operasi 1dan perubahan yang terhasil daripada operasi ini kelihatan kepada benang Bapabila operasi 2dijalankan dan selepas itu. Setiap peraturan menjamin bahawa apabila anda menulis atur cara berbilang benang, peristiwa tertentu akan berlaku sebelum yang lain 100% sepanjang masa, dan pada masa operasi 2benang Bakan sentiasa menyedari perubahan yang Adibuat semasa operasi 1. Mari kita semak mereka.

Peraturan 1.

Melepaskan mutex berlaku sebelum monitor yang sama diperoleh oleh utas lain. Saya fikir anda memahami segala-galanya di sini. Jika mutex objek atau kelas diperoleh oleh satu utas., sebagai contoh, dengan benang A, satu lagi benang (benang B) tidak boleh memperolehnya pada masa yang sama. Ia mesti menunggu sehingga mutex dilepaskan.

Peraturan 2.

Thread.start()Kaedah itu berlaku sebelum Thread.run() . Sekali lagi, tiada yang sukar di sini. Anda sudah tahu bahawa untuk mula menjalankan kod di dalam run()kaedah, anda mesti memanggil start()kaedah pada benang. Secara khusus, kaedah permulaan, bukan run()kaedah itu sendiri! Peraturan ini memastikan bahawa nilai semua pembolehubah yang ditetapkan sebelum Thread.start()dipanggil akan kelihatan di dalam run()kaedah sebaik sahaja dimulakan.

Peraturan 3.

Penghujung kaedah run()berlaku sebelum pulangan daripada join()kaedah. Mari kembali ke dua utas kami: Adan B. Kami memanggil join()kaedah tersebut supaya benang Bdijamin menunggu selesainya benang Asebelum ia berfungsi. Ini bermakna kaedah objek A run()dijamin berjalan hingga ke penghujungnya. Dan semua perubahan pada data yang berlaku dalam run()kaedah utas Aadalah seratus peratus dijamin akan kelihatan dalam utas Bsebaik sahaja ia selesai menunggu utas Auntuk menyelesaikan kerjanya supaya ia boleh memulakan kerjanya sendiri.

Peraturan 4.

Menulis kepada volatilepembolehubah berlaku sebelum membaca daripada pembolehubah yang sama. Apabila kita menggunakan volatilekata kunci, kita sebenarnya sentiasa mendapat nilai semasa. Walaupun dengan longatau double(kami bercakap sebelum ini tentang masalah yang boleh berlaku di sini). Seperti yang telah anda fahami, perubahan yang dibuat pada beberapa utas tidak selalu kelihatan kepada utas lain. Tetapi, sudah tentu, terdapat situasi yang sangat kerap di mana tingkah laku sedemikian tidak sesuai dengan kita. Katakan kita memberikan nilai kepada pembolehubah pada benang A:

int z;

….

z = 555;
Jika Butas kami harus memaparkan nilai pembolehubah zpada konsol, ia boleh memaparkan 0 dengan mudah, kerana ia tidak tahu tentang nilai yang diberikan. Tetapi Peraturan 4 menjamin bahawa jika kami mengisytiharkan pembolehubah zsebagai volatile, maka perubahan kepada nilainya pada satu urutan akan sentiasa kelihatan pada urutan lain. Jika kita menambah perkataan volatilepada kod sebelumnya...

volatile int z;

….

z = 555;
...maka kami menghalang situasi di mana benang Bmungkin memaparkan 0. Menulis kepada volatilepembolehubah berlaku sebelum membaca daripadanya.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION