JavaRush /Blog Java /Random-MS /Pengurusan aliran. Kata kunci yang tidak menentu dan kaed...

Pengurusan aliran. Kata kunci yang tidak menentu dan kaedah hasil().

Diterbitkan dalam kumpulan
hello! Kami terus mengkaji multithreading, dan hari ini kami akan berkenalan dengan kata kunci baharu - tidak menentu dan kaedah yield(). Mari kita fikirkan apa itu :)

Kata kunci tidak menentu

Apabila mencipta aplikasi berbilang benang, kita boleh menghadapi dua masalah yang serius. Pertama, semasa operasi aplikasi berbilang benang, benang yang berbeza boleh menyimpan nilai pembolehubah (kita akan bercakap lebih lanjut mengenai perkara ini dalam kuliah "Menggunakan tidak menentu" ). Ada kemungkinan bahawa satu utas mengubah nilai pembolehubah, tetapi yang kedua tidak melihat perubahan ini kerana ia berfungsi dengan salinan pembolehubah cachenya sendiri. Sememangnya, akibatnya boleh menjadi serius. Bayangkan bahawa ini bukan hanya sejenis "pembolehubah", tetapi, sebagai contoh, baki kad bank anda, yang tiba-tiba mula melompat ke sana ke mari secara rawak :) Tidak begitu menyenangkan, bukan? Kedua, di Jawa, operasi baca dan tulis pada medan semua jenis kecuali longdan doubleadalah atom. Apakah atomicity? Contohnya, jika anda menukar nilai pembolehubah dalam satu utas int, dan dalam utas lain anda membaca nilai pembolehubah ini, anda akan mendapat sama ada nilai lamanya atau nilai baharu - nilai yang muncul selepas perubahan dalam benang 1. Tiada "pilihan perantaraan" akan muncul di sana Mungkin. Walau bagaimanapun, ini tidak berfungsi dengan longdan . doublekenapa? Kerana ia merentas platform. Adakah anda masih ingat bagaimana kami mengatakan pada peringkat pertama bahawa prinsip Java "ditulis sekali, berfungsi di mana-mana"? Ini adalah platform silang. Iaitu, aplikasi Java berjalan pada platform yang sama sekali berbeza. Contohnya, pada sistem pengendalian Windows, versi Linux atau MacOS yang berbeza, dan di mana-mana sahaja aplikasi ini akan berfungsi dengan stabil. longdan double- primitif paling "berat" di Jawa: beratnya 64 bit. Dan sesetengah platform 32-bit semata-mata tidak melaksanakan atomicity membaca dan menulis pembolehubah 64-bit. Pembolehubah sedemikian dibaca dan ditulis dalam dua operasi. Pertama, 32 bit pertama ditulis kepada pembolehubah, kemudian 32 lagi. Oleh itu, dalam kes ini masalah mungkin timbul. Satu benang menulis beberapa nilai 64-bit kepada pembolehubahХ, dan dia melakukannya "dalam dua langkah." Pada masa yang sama, benang kedua cuba membaca nilai pembolehubah ini, dan melakukannya betul-betul di tengah, apabila 32 bit pertama telah ditulis, tetapi yang kedua belum lagi ditulis. Akibatnya, ia membaca nilai pertengahan, salah dan ralat berlaku. Sebagai contoh, jika pada platform sedemikian kami cuba menulis nombor kepada pembolehubah - 9223372036854775809 - ia akan menduduki 64 bit. Dalam bentuk binari ia akan kelihatan seperti ini: 1000000000000000000000000000000000000000000000000000000000000001 Benang pertama akan mula menulis nombor ini kepada pembolehubah, dan mula-mula akan menulis 000000 pertama: 00000000000000 000000 00000 dan kemudian yang kedua 32: 0000000000000000000000000000001 Dan benang kedua boleh menjejak ke dalam jurang ini dan baca nilai perantaraan pembolehubah - 10000000000000000000000000000000, 32 bit pertama yang telah ditulis. Dalam sistem perpuluhan, nombor ini bersamaan dengan 2147483648. Iaitu, kami hanya mahu menulis nombor 9223372036854775809 ke dalam pembolehubah, tetapi disebabkan oleh fakta bahawa operasi ini pada beberapa platform bukan atom, kami mendapat nombor "kiri" 2147483648 , yang kami tidak perlukan, entah dari mana. dan tidak diketahui bagaimana ia akan menjejaskan pengendalian program. Benang kedua hanya membaca nilai pembolehubah sebelum ia akhirnya ditulis, iaitu, ia melihat 32 bit pertama, tetapi bukan 32 bit kedua. Masalah ini, sudah tentu, tidak timbul semalam, dan di Jawa ia diselesaikan menggunakan hanya satu kata kunci - volatile . Jika kami mengisytiharkan beberapa pembolehubah dalam program kami dengan perkataan tidak menentu...
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 64-bit doubleatau long.
  2. Mesin Java tidak akan menyimpannya di cache. Jadi keadaan apabila 10 utas berfungsi dengan salinan tempatan mereka dikecualikan.
Ini adalah bagaimana dua masalah yang sangat serius diselesaikan dalam satu perkataan :)

kaedah hasil().

Kami telah melihat banyak kaedah kelas Thread, tetapi ada satu kaedah penting yang akan menjadi baharu kepada anda. Ini ialah kaedah yield() . Diterjemahkan daripada bahasa Inggeris sebagai “give in.” Dan itulah yang dilakukan oleh kaedah itu! Pengurusan aliran.  Kata kunci yang tidak menentu dan kaedah yield() - 2Apabila kita memanggil kaedah hasil pada utas, ia sebenarnya berkata kepada utas lain: “Baiklah, kawan-kawan, saya tidak tergesa-gesa, jadi jika penting bagi mana-mana daripada anda untuk mendapatkan masa CPU, ambillah, saya tidak mendesak.” Berikut ialah contoh mudah cara ia berfungsi:
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();
   }
}
Kami mencipta dan melancarkan tiga utas secara berurutan - Thread-0, Thread-1dan Thread-2. Thread-0bermula dahulu dan segera memberi laluan kepada orang lain. Selepas itu ia bermula Thread-1, dan juga memberi laluan. Selepas itu, ia bermula Thread-2, yang juga lebih rendah. Kami tidak mempunyai apa-apa lagi benang, dan selepas Thread-2yang terakhir meninggalkan tempatnya, penjadual benang kelihatan: "Jadi, tiada lagi benang baharu, siapa yang kita ada dalam baris gilir? Siapa yang terakhir melepaskan tempat mereka sebelum ini Thread-2? Saya fikir ia adalah Thread-1? Baiklah, biarlah ia selesai.” Thread-1melakukan tugasnya sehingga tamat, selepas itu penjadual benang terus menyelaras: “Baiklah, Thread-1 telah selesai. Adakah kita mempunyai orang lain dalam barisan?" Terdapat Thread-0 dalam baris gilir: ia meninggalkan tempatnya serta-merta sebelum Thread-1. Sekarang perkara itu telah datang kepadanya, dan dia sedang dijalankan hingga akhir. Selepas itu penjadual selesai menyelaraskan utas: “Baiklah, Thread-2, anda memberi laluan kepada utas lain, semuanya telah berfungsi. Anda adalah yang terakhir memberi laluan, jadi sekarang giliran anda.” Selepas ini, Thread-2 berjalan hingga selesai. Output konsol akan kelihatan seperti ini: Thread-0 memberi laluan kepada yang lain Thread-1 memberi laluan kepada yang lain Thread-2 memberi laluan kepada yang lain Thread-1 telah selesai dilaksanakan. Thread-0 telah selesai dilaksanakan. Thread-2 telah selesai dilaksanakan. Penjadual benang sudah tentu boleh menjalankan benang dalam susunan yang berbeza (contohnya, 2-1-0 bukannya 0-1-2), tetapi prinsipnya adalah sama.

Berlaku-sebelum peraturan

Perkara terakhir yang akan kita sentuh hari ini ialah prinsip " berlaku sebelum ". Seperti yang anda sedia maklum, di Jawa, kebanyakan kerja memperuntukkan masa dan sumber kepada benang untuk menyelesaikan tugasnya dilakukan oleh penjadual benang. Anda juga telah melihat lebih daripada sekali cara urutan dilaksanakan dalam susunan sewenang-wenangnya, dan selalunya mustahil untuk meramalkannya. Dan secara umum, selepas pengaturcaraan "berurutan" yang kami lakukan sebelum ini, multithreading kelihatan seperti perkara rawak. Seperti yang telah anda lihat, kemajuan program berbilang benang boleh dikawal menggunakan satu set keseluruhan kaedah. Tetapi sebagai tambahan kepada ini, dalam multithreading Java terdapat satu lagi "pulau kestabilan" - 4 peraturan yang dipanggil " berlaku-sebelum ". Dari bahasa Inggeris, ini diterjemahkan sebagai "berlaku sebelum", atau "berlaku sebelum". Maksud peraturan ini agak mudah difahami. Bayangkan kita mempunyai dua utas - Adan B. Setiap utas ini boleh melakukan operasi 1dan 2. Dan apabila dalam setiap peraturan kita sebut " A berlaku-sebelum B ", ini bermakna semua perubahan yang dibuat oleh benang Asebelum operasi 1dan perubahan yang melibatkan operasi ini kelihatan kepada benang Bpada masa operasi dijalankan 2dan selepas pembedahan dijalankan. Setiap peraturan ini memastikan bahawa semasa menulis atur cara berbilang benang, beberapa peristiwa akan berlaku sebelum yang lain 100% sepanjang masa, dan urutan Bsemasa operasi 2akan sentiasa menyedari perubahan yang Аdibuat oleh benang semasa operasi. 1. Mari lihat mereka.

Peraturan 1.

Melepaskan mutex berlaku sebelum berlaku sebelum thread lain memperoleh monitor yang sama. Nah, semuanya nampak jelas di sini. Jika muteks objek atau kelas diperoleh oleh satu utas, sebagai contoh, utas А, satu lagi utas (benang B) tidak boleh memperolehnya pada masa yang sama. Anda perlu menunggu sehingga mutex dilepaskan.

Peraturan 2.

Kaedah Thread.start() yang berlaku sebelum Thread.run() . Tiada yang rumit juga. Anda sudah tahu: agar kod di dalam kaedah mula melaksanakan run(), anda perlu memanggil kaedah pada benang start(). Ia adalah miliknya, dan bukan kaedah itu sendiri run()! Peraturan ini memastikan bahawa Thread.start()nilai semua pembolehubah yang ditetapkan sebelum pelaksanaan akan dapat dilihat di dalam kaedah yang mula dilaksanakan run().

Peraturan 3.

Penyelesaian kaedah run() berlaku sebelum kaedah keluar join(). Mari kembali ke dua aliran kita - Аdan B. Kami memanggil kaedah join()sedemikian rupa sehingga benang Bmesti menunggu sehingga selesai Asebelum melakukan kerjanya. Ini bermakna kaedah run()objek A pasti akan berjalan sehingga akhir. Dan semua perubahan dalam data yang berlaku dalam kaedah run()utas Aakan kelihatan sepenuhnya dalam utas Bapabila ia menunggu untuk siap Adan mula berfungsi dengan sendirinya.

Peraturan 4.

Menulis kepada pembolehubah yang tidak menentu berlaku sebelum membaca daripada pembolehubah yang sama. Dengan menggunakan kata kunci yang tidak menentu, kita sebenarnya akan sentiasa mendapat nilai semasa. Walaupun dalam kes longdan double, masalah yang telah dibincangkan sebelum ini. Seperti yang anda sudah fahami, perubahan yang dibuat dalam sesetengah utas tidak selalu kelihatan kepada utas lain. Tetapi, sudah tentu, selalunya terdapat situasi apabila tingkah laku program sedemikian tidak sesuai dengan kita. Katakan kita memberikan nilai kepada pembolehubah dalam benang A:
int z;.

z= 555;
Jika utas kami Bmencetak nilai pembolehubah zke konsol, ia boleh mencetak 0 dengan mudah kerana ia tidak mengetahui tentang nilai yang diberikan kepadanya. Jadi, Peraturan 4 menjamin kami: jika anda mengisytiharkan pembolehubah zsebagai tidak menentu, perubahan pada nilainya dalam satu utas akan sentiasa kelihatan dalam utas lain. Jika kita menambah perkataan yang tidak menentu pada kod sebelumnya...
volatile int z;.

z= 555;
...keadaan di mana aliran Bakan mengeluarkan 0 ke konsol dikecualikan. Menulis kepada pembolehubah tidak menentu berlaku sebelum membaca daripadanya.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION