CodeGym /Java Blog /Acak /Mengelola utas. Kata kunci volatile dan metode yield()
John Squirrels
Level 41
San Francisco

Mengelola utas. Kata kunci volatile dan metode yield()

Dipublikasikan di grup Acak
Hai! Kami melanjutkan studi kami tentang multithreading. Hari ini kita akan mengenal volatilekata kunci dan yield()metodenya. Mari selami :)

Kata kunci yang mudah menguap

Saat membuat aplikasi multithreaded, kita dapat mengalami dua masalah serius. Pertama, saat aplikasi multithreaded sedang berjalan, thread yang berbeda dapat meng-cache nilai variabel (kita telah membahasnya dalam pelajaran berjudul 'Menggunakan volatile' ). Anda dapat mengalami situasi di mana satu utas mengubah nilai suatu variabel, tetapi utas kedua tidak melihat perubahannya, karena ia bekerja dengan salinan variabel yang di-cache. Secara alami, konsekuensinya bisa serius. Misalkan itu bukan sembarang variabel lama melainkan saldo rekening bank Anda, yang tiba-tiba mulai melompat-lompat secara acak :) Kedengarannya tidak menyenangkan, bukan? Kedua, di Java, operasi untuk membaca dan menulis semua tipe primitif,longdouble, adalah atom. Nah, misalnya, jika Anda mengubah nilai suatu intvariabel di satu utas, dan di utas lain Anda membaca nilai variabel tersebut, Anda akan mendapatkan nilai lama atau yang baru, yaitu nilai yang dihasilkan dari perubahan tersebut. di utas 1. Tidak ada 'nilai tengah'. Namun, ini tidak bekerja dengan longs dan doubles. Mengapa? Karena dukungan lintas platform. Ingat pada level awal yang kami katakan bahwa prinsip panduan Java adalah 'menulis sekali, jalankan di mana saja'? Itu berarti dukungan lintas platform. Dengan kata lain, aplikasi Java berjalan di semua jenis platform yang berbeda. Misalnya, pada sistem operasi Windows, versi Linux atau MacOS berbeda. Ini akan berjalan tanpa hambatan pada mereka semua. Menimbang dalam 64 bit,longdoubleadalah primitif 'terberat' di Jawa. Dan platform 32-bit tertentu tidak mengimplementasikan pembacaan dan penulisan atom variabel 64-bit. Variabel tersebut dibaca dan ditulis dalam dua operasi. Pertama, 32 bit pertama ditulis ke variabel, dan kemudian 32 bit lainnya ditulis. Akibatnya, masalah mungkin muncul. Satu utas menulis beberapa nilai 64-bit ke Xvariabel dan melakukannya dalam dua operasi. Pada saat yang sama, utas kedua mencoba membaca nilai variabel dan melakukannya di antara dua operasi tersebut - ketika 32 bit pertama telah ditulis, tetapi 32 bit kedua belum. Akibatnya, ia membaca nilai perantara yang salah, dan kami memiliki bug. Misalnya, jika pada platform seperti itu kami mencoba menulis nomor ke 9223372036854775809 ke variabel, itu akan menempati 64 bit. Dalam formulir biner, sepertinya ini: 10000000000000000000000000000000000000000000000000000000000000000000000 Utas pertama mulai menulis angka ke variabel. Pada awalnya, ia menulis 32 bit pertama (100000000000000000000000000000000) dan kemudian 32 bit kedua (00000000000000000000000000000001) Dan utas kedua bisa terjepit di antara operasi ini, membaca nilai antara variabel (100000000000000000000000000000000000000000000000), yang merupakan 32 bit pertama yang telah ditulis. Dalam sistem desimal, angka ini adalah 2.147.483.648. Dengan kata lain, kami hanya ingin menulis angka 9223372036854775809 ke sebuah variabel, tetapi karena fakta bahwa operasi ini tidak atomik pada beberapa platform, kami memiliki angka jahat 2.147.483.648, yang muncul entah dari mana dan akan memiliki efek yang tidak diketahui program. Utas kedua hanya membaca nilai variabel sebelum selesai ditulis, yaitu utas melihat 32 bit pertama, tetapi bukan 32 bit kedua. Tentu saja, masalah ini tidak muncul kemarin. Java menyelesaikannya dengan satu kata kunci: volatile. Jika kita menggunakanvolatilekata kunci saat mendeklarasikan beberapa variabel dalam program kita…

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…artinya:
  1. Itu akan selalu dibaca dan ditulis secara atomik. Bahkan jika itu 64-bit doubleatau long.
  2. Mesin Java tidak akan menyimpannya. Jadi Anda tidak akan mengalami situasi di mana 10 utas bekerja dengan salinan lokalnya sendiri.
Jadi, dua masalah yang sangat serius diselesaikan hanya dengan satu kata :)

Metode hasil()

Kami telah meninjau banyak Threadmetode kelas, tetapi ada satu metode penting yang akan menjadi hal baru bagi Anda. Itu yield()metodenya . Dan itu persis seperti namanya! Mengelola utas.  Kata kunci volatile dan metode yield() - 2Saat kami memanggil yieldmetode di utas, itu sebenarnya berbicara ke utas lainnya: 'Hai, teman-teman. Saya tidak terburu-buru untuk pergi ke mana pun, jadi jika penting bagi salah satu dari Anda untuk mendapatkan waktu prosesor, ambillah — saya bisa menunggu '. Berikut ini contoh sederhana cara kerjanya:

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 secara berurutan membuat dan memulai tiga utas: Thread-0, Thread-1, dan Thread-2. Thread-0mulai lebih dulu dan langsung mengalah kepada yang lain. Kemudian Thread-1dimulai dan juga menghasilkan. Kemudian Thread-2dimulai, yang juga menghasilkan. Kami tidak memiliki utas lagi, dan setelah Thread-2memberikan tempatnya terakhir, penjadwal utas mengatakan, 'Hmm, tidak ada lagi utas baru. Siapa yang kita miliki dalam antrian? Siapa yang menyerahkan tempatnya sebelumnya Thread-2? Sepertinya begitu Thread-1. Oke, itu artinya kita akan membiarkannya berjalan '. Thread-1menyelesaikan pekerjaannya dan kemudian penjadwal utas melanjutkan koordinasinya: 'Oke, Thread-1selesai. Apakah kita memiliki orang lain dalam antrean?'. Thread-0 ada di antrian: itu menghasilkan tempatnya tepat sebelumnyaThread-1. Sekarang giliran dan berjalan sampai selesai. Kemudian penjadwal selesai mengoordinasikan utas: 'Oke, Thread-2Anda menyerah pada utas lain, dan semuanya selesai sekarang. Kamu yang terakhir mengalah, jadi sekarang giliranmu'. Kemudian Thread-2berjalan sampai selesai. Output konsol akan terlihat seperti ini: Thread-0 memberikan tempatnya kepada orang lain Thread-1 memberikan tempatnya kepada orang lain Thread-2 memberikan tempatnya kepada orang lain Thread-1 telah selesai dieksekusi. Thread-0 telah selesai dieksekusi. Thread-2 telah selesai dieksekusi. Tentu saja, penjadwal utas mungkin memulai utas dalam urutan yang berbeda (misalnya, 2-1-0 alih-alih 0-1-2), tetapi prinsipnya tetap sama.

Terjadi-sebelum aturan

Hal terakhir yang akan kita sentuh hari ini adalah konsep ' terjadi sebelum '. Seperti yang sudah Anda ketahui, di Java, penjadwal utas melakukan sebagian besar pekerjaan yang terlibat dalam mengalokasikan waktu dan sumber daya ke utas untuk melakukan tugasnya. Anda juga telah berulang kali melihat bagaimana utas dieksekusi dalam urutan acak yang biasanya tidak dapat diprediksi. Dan secara umum, setelah pemrograman 'berurutan' yang kami lakukan sebelumnya, pemrograman multithread terlihat seperti sesuatu yang acak. Anda sudah percaya bahwa Anda dapat menggunakan sejumlah metode untuk mengontrol aliran program multithreaded. Tetapi multithreading di Java memiliki satu pilar lagi — aturan 4 ' terjadi sebelum '. Memahami aturan ini cukup sederhana. Bayangkan kita memiliki dua utas — AdanB. Setiap utas ini dapat melakukan operasi 1dan 2. Dalam setiap aturan, ketika kami mengatakan ' A terjadi-sebelum B ', kami bermaksud bahwa semua perubahan yang dilakukan oleh utas Asebelum operasi 1dan perubahan yang dihasilkan dari operasi ini dapat dilihat oleh utas Bsaat operasi 2dilakukan dan sesudahnya. Setiap aturan menjamin bahwa ketika Anda menulis program multithreaded, peristiwa tertentu akan terjadi sebelum yang lain 100% dari waktu, dan pada saat operasi 2thread Bakan selalu mengetahui perubahan yang Adilakukan thread selama operasi 1. Mari kita tinjau.

Aturan 1.

Melepaskan mutex terjadi sebelum monitor yang sama diakuisisi oleh utas lainnya. Saya pikir Anda mengerti semuanya di sini. Jika mutex objek atau kelas diperoleh dengan satu utas, misalnya, dengan utas A, utas lain (utas B) tidak dapat memperolehnya pada saat yang sama. Itu harus menunggu sampai mutex dilepaskan.

Aturan 2.

Thread.start()Metode ini terjadi sebelumnya Thread.run() . Sekali lagi, tidak ada yang sulit di sini. Anda sudah tahu bahwa untuk mulai menjalankan kode di dalam run()metode, Anda harus memanggil start()metode di utas. Khususnya, metode mulai, bukan run()metode itu sendiri! Aturan ini memastikan bahwa nilai semua variabel yang ditetapkan sebelum Thread.start()dipanggil akan terlihat di dalam run()metode begitu dimulai.

Aturan 3.

Akhir dari run()metode terjadi sebelum pengembalian dari join()metode. Mari kembali ke dua utas kita: Adan B. Kami memanggil join()metode sehingga utas Bdijamin menunggu selesainya utas Asebelum bekerja. Ini berarti bahwa metode objek A run()dijamin berjalan sampai akhir. Dan semua perubahan pada data yang terjadi dalam run()metode utas Adijamin seratus persen akan terlihat di utas Bsetelah selesai menunggu utas Amenyelesaikan pekerjaannya sehingga dapat memulai pekerjaannya sendiri.

Aturan 4.

Menulis ke volatilevariabel terjadi sebelum membaca dari variabel yang sama. Saat kami menggunakan volatilekata kunci, kami sebenarnya selalu mendapatkan nilai saat ini. Bahkan dengan longatau double(kami berbicara sebelumnya tentang masalah yang bisa terjadi di sini). Seperti yang sudah Anda pahami, perubahan yang dilakukan pada beberapa utas tidak selalu terlihat oleh utas lainnya. Tapi, tentu saja, sangat sering ada situasi di mana perilaku seperti itu tidak cocok untuk kita. Misalkan kita menetapkan nilai ke variabel di thread A:

int z;

….

z = 555;
Jika Butas kami harus menampilkan nilai variabel zdi konsol, itu dapat dengan mudah menampilkan 0, karena tidak tahu tentang nilai yang diberikan. Tetapi Aturan 4 menjamin bahwa jika kita mendeklarasikan zvariabel sebagai volatile, maka perubahan nilainya di satu utas akan selalu terlihat di utas lainnya. Jika kita menambahkan kata volatileke kode sebelumnya...

volatile int z;

….

z = 555;
... lalu kami mencegah situasi di mana utas Bmungkin menampilkan 0. Penulisan ke volatilevariabel terjadi sebelum membacanya.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION