JavaRush /Java Blog /Random-ID /Manajemen aliran. Kata kunci yang mudah menguap dan metod...

Manajemen aliran. Kata kunci yang mudah menguap dan metode hasil()

Dipublikasikan di grup Random-ID
Halo! Kami terus mempelajari multithreading, dan hari ini kami akan berkenalan dengan kata kunci baru - metode volatil dan hasil(). Mari kita cari tahu apa itu :)

Kata kunci mudah berubah

Saat membuat aplikasi multi-thread, kita mungkin menghadapi dua masalah serius. Pertama, selama pengoperasian aplikasi multi-thread, thread yang berbeda dapat menyimpan nilai variabel dalam cache (kita akan membicarakan hal ini lebih lanjut dalam kuliah “Menggunakan volatil” ). Ada kemungkinan bahwa satu thread mengubah nilai suatu variabel, tetapi thread kedua tidak melihat perubahan ini karena thread tersebut bekerja dengan salinan variabel yang di-cache sendiri. Tentu saja, konsekuensinya bisa serius. Bayangkan ini bukan sekadar “variabel”, tetapi, misalnya, saldo kartu bank Anda, yang tiba-tiba mulai melonjak-lonjak secara acak :) Sangat tidak menyenangkan bukan? Kedua, di Java, operasi baca dan tulis pada semua jenis bidang kecuali longdan doublebersifat atomik. Apa itu atomisitas? Misalnya, jika Anda mengubah nilai suatu variabel di satu utas int, dan di utas lain Anda membaca nilai variabel ini, Anda akan mendapatkan nilai lama atau nilai baru - nilai yang diperoleh setelah perubahan di thread 1. Tidak ada "opsi perantara" yang akan muncul di sana. Mungkin. Namun hal ini tidak dapat dilakukan dengan longdan . doubleMengapa? Karena ini lintas platform. Apakah Anda ingat bagaimana kami mengatakan di tingkat pertama bahwa prinsip Java adalah “ditulis sekali, berfungsi di mana saja”? Ini adalah lintas platform. Artinya, aplikasi Java berjalan pada platform yang sangat berbeda. Misalnya, pada sistem operasi Windows, versi Linux atau MacOS yang berbeda, dan di mana pun aplikasi ini akan bekerja dengan stabil. longdan double- primitif paling "berat" di Jawa: beratnya 64 bit. Dan beberapa platform 32-bit tidak menerapkan atomisitas membaca dan menulis variabel 64-bit. Variabel tersebut dibaca dan ditulis dalam dua operasi. Pertama, 32 bit pertama ditulis ke variabel, lalu 32 bit lainnya. Oleh karena itu, dalam kasus ini masalah mungkin timbul. Satu thread menulis beberapa nilai 64-bit ke suatu variabelХ, dan dia melakukannya “dalam dua langkah.” Pada saat yang sama, thread kedua mencoba membaca nilai variabel ini, dan melakukannya tepat di tengah, ketika 32 bit pertama telah ditulis, tetapi bit kedua belum ditulis. Akibatnya, ia membaca nilai perantara yang salah, dan terjadi kesalahan. Misalnya, jika pada platform seperti itu kita mencoba menulis angka ke variabel - 9223372036854775809 - itu akan menempati 64 bit. Dalam bentuk biner akan terlihat seperti ini: 10000000000000000000000000000000000000000000000000000000000000000001 Thread pertama akan mulai menulis angka ini ke variabel, dan pertama-tama akan menulis 32 bit pertama: 10000000000000000000000 00000 00000 dan kemudian yang kedua 32: 000000000000000000000000000000001 Dan benang kedua dapat masuk ke celah ini dan membaca nilai antara variabel - 100000000000000000000000000000000, 32 bit pertama yang telah ditulis. Dalam sistem desimal, angka ini sama dengan 2147483648. Artinya, kami hanya ingin menulis angka 9223372036854775809 ke dalam variabel, tetapi karena operasi ini pada beberapa platform bukan atomik, kami mendapatkan angka "kiri" 2147483648 , yang tidak kita perlukan, entah dari mana, dan tidak diketahui bagaimana pengaruhnya terhadap pengoperasian program. Thread kedua hanya membaca nilai variabel sebelum akhirnya ditulis, yaitu melihat 32 bit pertama, tetapi tidak melihat 32 bit kedua. Masalah ini, tentu saja, tidak muncul kemarin, dan di Java masalah tersebut diselesaikan hanya dengan menggunakan satu kata kunci - volatil . Jika kita mendeklarasikan beberapa variabel dalam program kita dengan kata volatil...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…artinya:
  1. Itu akan selalu dibaca dan ditulis secara atom. Meskipun itu 64-bit doubleatau long.
  2. Mesin Java tidak akan menyimpannya dalam cache. Jadi situasi ketika 10 thread bekerja dengan salinan lokalnya dikecualikan.
Beginilah cara dua masalah yang sangat serius diselesaikan dalam satu kata :)

hasil() metode

Kita telah melihat banyak metode kelas Thread, tetapi ada satu metode penting yang mungkin baru bagi Anda. Ini adalah metode hasil() . Diterjemahkan dari bahasa Inggris sebagai “menyerah.” Dan itulah yang dilakukan metode ini! Manajemen aliran.  Kata kunci yang mudah menguap dan metode hasil() - 2Saat kita memanggil metode hasil pada sebuah thread, metode ini sebenarnya mengatakan kepada thread lain: “Oke, teman-teman, saya tidak sedang terburu-buru, jadi jika penting bagi Anda untuk mendapatkan waktu CPU, ambillah, saya tidak penting." Berikut ini contoh sederhana cara kerjanya:
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 secara berurutan membuat dan meluncurkan tiga utas - Thread-0, Thread-1dan Thread-2. Thread-0dimulai terlebih dahulu dan segera memberi jalan kepada orang lain. Setelah itu dimulai Thread-1, dan juga memberi jalan. Setelah itu, dimulai Thread-2, yang juga lebih rendah. Kami tidak memiliki thread lagi, dan setelah Thread-2thread terakhir menyerahkan tempatnya, penjadwal thread akan melihat: “Jadi, tidak ada lagi thread baru, siapa yang ada di antrean kami? Siapa yang terakhir menyerahkan tempatnya sebelumnya Thread-2? Menurutku itu tadi Thread-1? Oke, biarlah itu selesai.” Thread-1melakukan tugasnya sampai akhir, setelah itu penjadwal thread terus berkoordinasi: “Oke, Thread-1 sudah selesai. Apa ada orang lain yang mengantre?" Ada Thread-0 dalam antrian: ia menyerahkan tempatnya tepat sebelum Thread-1. Sekarang masalahnya sudah sampai padanya, dan dia sedang dibawa sampai akhir. Setelah itu penjadwal selesai mengoordinasikan thread: “Oke, Thread-2, Anda memberi jalan ke thread lain, semuanya sudah berfungsi. Kamu adalah orang terakhir yang memberi jalan, jadi sekarang giliranmu.” Setelah ini, Thread-2 berjalan hingga selesai. Output konsol akan terlihat seperti ini: Thread-0 memberi jalan kepada yang lain. Thread-1 memberi jalan kepada yang lain. Thread-2 memberi jalan kepada yang lain. Thread-1 telah selesai dieksekusi. Thread-0 telah selesai dieksekusi. Thread-2 telah selesai dieksekusi. Penjadwal thread, tentu saja, dapat menjalankan thread dalam urutan yang berbeda (misalnya, 2-1-0, bukan 0-1-2), tetapi prinsipnya sama.

Terjadi-sebelum aturan

Hal terakhir yang akan kita bahas hari ini adalah prinsip " terjadi sebelum ". Seperti yang sudah Anda ketahui, di Java, sebagian besar pekerjaan mengalokasikan waktu dan sumber daya ke thread untuk menyelesaikan tugasnya dilakukan oleh penjadwal thread. Anda juga telah melihat lebih dari sekali bagaimana thread dieksekusi dalam urutan yang sewenang-wenang, dan seringkali tidak mungkin untuk memprediksinya. Dan secara umum, setelah pemrograman “sekuensial” yang kita lakukan sebelumnya, multithreading terlihat seperti hal yang acak. Seperti yang telah Anda lihat, kemajuan program multithread dapat dikontrol menggunakan berbagai metode. Namun selain itu, di multithreading Java ada “pulau stabilitas” lainnya - 4 aturan yang disebut “ terjadi sebelum ”. Secara harfiah dari bahasa Inggris ini diterjemahkan sebagai “terjadi sebelumnya”, atau “terjadi sebelumnya”. Arti dari aturan-aturan ini cukup sederhana untuk dipahami. Bayangkan kita memiliki dua utas - Adan B. Masing-masing thread ini dapat melakukan operasi 1dan 2. Dan ketika di setiap aturan kita mengatakan “ A terjadi-sebelum B ”, ini berarti bahwa semua perubahan yang dilakukan oleh thread Asebelum operasi 1dan perubahan yang diperlukan oleh operasi ini terlihat oleh thread Bpada saat operasi dilakukan 2dan setelah operasi dilakukan. Masing-masing aturan ini memastikan bahwa ketika menulis program multi-thread, beberapa peristiwa akan terjadi 100% lebih awal dari yang lain, dan bahwa thread Bpada saat operasi 2akan selalu mengetahui perubahan yang Аdibuat thread selama operasi. 1. Mari kita lihat mereka.

Aturan 1.

Pelepasan mutex terjadi sebelum thread lain memperoleh monitor yang sama. Ya, semuanya tampak jelas di sini. Jika mutex suatu objek atau kelas diperoleh oleh satu thread, misalnya thread А, thread lain (thread B) tidak dapat memperolehnya secara bersamaan. Anda harus menunggu hingga mutex dilepaskan.

Aturan 2.

Thread.start() Ini terjadi sebelum method Thread.run(). Tidak ada yang rumit juga. Anda sudah tahu: agar kode di dalam metode mulai dieksekusi run(), Anda perlu memanggil metode di thread start(). Itu miliknya, dan bukan metodenya sendiri run()! Aturan ini memastikan bahwa Thread.start()nilai semua variabel yang ditetapkan sebelum eksekusi akan terlihat di dalam metode yang mulai dijalankan run().

Aturan 3.

Penyelesaian metode run() terjadi sebelum metode keluar join(). Mari kita kembali ke dua aliran kita - Аdan B. Kami memanggil metode join()sedemikian rupa sehingga thread Bharus menunggu hingga selesai Asebelum melakukan tugasnya. Artinya metode run()objek A pasti akan berjalan sampai akhir. Dan segala perubahan data yang terjadi pada metode run()thread Aakan terlihat sepenuhnya pada thread Bketika menunggu selesai Adan mulai bekerja sendiri.

Aturan 4.

Penulisan ke variabel volatil terjadi sebelum pembacaan dari variabel yang sama. Dengan menggunakan kata kunci volatil, sebenarnya kita akan selalu mendapatkan nilai saat ini. Bahkan dalam kasus longdan double, permasalahan yang telah dibahas sebelumnya. Seperti yang sudah Anda pahami, perubahan yang dilakukan di beberapa thread tidak selalu terlihat oleh thread lainnya. Namun, tentu saja, sering kali ada situasi di mana perilaku program seperti itu tidak sesuai dengan kita. Katakanlah kita memberikan nilai pada variabel di thread A:
int z;.

z= 555;
Jika thread kita Bmencetak nilai suatu variabel zke konsol, thread tersebut dapat dengan mudah mencetak 0 karena thread tersebut tidak mengetahui nilai yang diberikan padanya. Jadi, Aturan 4 menjamin kita: jika Anda mendeklarasikan suatu variabel zsebagai variabel yang mudah menguap, perubahan nilainya di satu thread akan selalu terlihat di thread lain. Jika kita menambahkan kata volatil ke kode sebelumnya...
volatile int z;.

z= 555;
...situasi di mana aliran Bakan menghasilkan 0 ke konsol tidak termasuk. Penulisan ke variabel volatil terjadi sebelum pembacaannya.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION