JavaRush /Blog Java /Random-MS /Menguruskan turun naik
lexmirnov
Tahap
Москва

Menguruskan turun naik

Diterbitkan dalam kumpulan

Garis Panduan Menggunakan Pembolehubah Meruap

Oleh Brian Goetz 19 Jun 2007 Asal: Menguruskan Kemeruapan Pembolehubah tidak menentu di Jawa boleh dipanggil "cahaya disegerakkan"; Mereka memerlukan kurang kod untuk digunakan daripada blok disegerakkan, selalunya berjalan lebih pantas, tetapi hanya boleh melakukan sebahagian kecil daripada apa yang dilakukan oleh blok disegerakkan. Artikel ini membentangkan beberapa corak untuk menggunakan tidak menentu dengan berkesan—dan beberapa amaran tentang tempat yang tidak boleh digunakan. Kunci mempunyai dua ciri utama: pengecualian bersama (mutex) dan keterlihatan. Pengecualian bersama bermakna kunci hanya boleh dipegang oleh satu utas pada satu masa dan sifat ini boleh digunakan untuk melaksanakan protokol kawalan akses untuk sumber kongsi supaya hanya satu utas akan menggunakannya pada satu masa. Keterlihatan ialah isu yang lebih halus, tujuannya adalah untuk memastikan perubahan yang dibuat pada sumber awam sebelum kunci dilepaskan akan dapat dilihat oleh urutan seterusnya yang mengambil alih kunci itu. Jika penyegerakan tidak menjamin keterlihatan, benang boleh menerima nilai lapuk atau tidak betul untuk pembolehubah awam, yang akan membawa kepada beberapa masalah serius.
Pembolehubah tidak menentu
Pembolehubah tidak menentu mempunyai sifat keterlihatan yang disegerakkan, tetapi tidak mempunyai keatomannya. Ini bermakna bahawa benang akan secara automatik menggunakan nilai terkini pembolehubah tidak menentu. Ia boleh digunakan untuk keselamatan benang , tetapi dalam set kes yang sangat terhad: yang tidak memperkenalkan perhubungan antara berbilang pembolehubah atau antara nilai semasa dan masa depan pembolehubah. Oleh itu, tidak menentu sahaja tidak mencukupi untuk melaksanakan pembilang, mutex atau mana-mana kelas yang bahagian tidak berubahnya dikaitkan dengan berbilang pembolehubah (contohnya, "mula <=tamat"). Anda boleh memilih kunci yang tidak menentu untuk salah satu daripada dua sebab utama: kesederhanaan atau kebolehskalaan. Sesetengah binaan bahasa lebih mudah untuk ditulis sebagai kod program, dan kemudian dibaca dan difahami, apabila mereka menggunakan pembolehubah yang tidak menentu dan bukannya kunci. Selain itu, tidak seperti kunci, ia tidak boleh menyekat benang dan oleh itu kurang terdedah kepada isu kebolehskalaan. Dalam situasi di mana terdapat lebih banyak bacaan daripada menulis, pembolehubah tidak menentu boleh memberikan faedah prestasi berbanding kunci.
Syarat untuk penggunaan yang betul dari volatile
Anda boleh menggantikan kunci dengan yang tidak menentu dalam beberapa keadaan terhad. Untuk selamat thread, kedua-dua kriteria mesti dipenuhi:
  1. Apa yang ditulis kepada pembolehubah adalah bebas daripada nilai semasanya.
  2. Pembolehubah tidak mengambil bahagian dalam invarian dengan pembolehubah lain.
Ringkasnya, syarat ini bermakna bahawa nilai sah yang boleh ditulis kepada pembolehubah yang tidak menentu adalah bebas daripada mana-mana keadaan lain program, termasuk keadaan semasa pembolehubah. Syarat pertama tidak termasuk penggunaan pembolehubah tidak menentu sebagai pembilang selamat benang. Walaupun kenaikan (x++) kelihatan seperti operasi tunggal, ia sebenarnya merupakan keseluruhan urutan operasi baca-ubah suai-tulis yang mesti dilakukan secara atom, yang tidak menentu tidak disediakan. Operasi yang sah memerlukan nilai x kekal sama sepanjang operasi, yang tidak boleh dicapai menggunakan meruap. (Walau bagaimanapun, jika anda boleh memastikan bahawa nilai ditulis daripada satu utas sahaja, syarat pertama boleh ditinggalkan.) Dalam kebanyakan situasi, sama ada syarat pertama atau kedua akan dilanggar, menjadikan pembolehubah tidak menentu sebagai pendekatan yang kurang biasa digunakan untuk mencapai keselamatan benang daripada yang disegerakkan. Penyenaraian 1 menunjukkan kelas bukan-benang-selamat dengan julat nombor. Ia mengandungi invarian - sempadan bawah sentiasa kurang daripada atau sama dengan atas. @NotThreadSafe public class NumberRange { private int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public void setLower(int value) { if (value > upper) throw new IllegalArgumentException(...); lower = value; } public void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(...); upper = value; } } Memandangkan pembolehubah keadaan julat dihadkan dengan cara ini, ia tidak akan mencukupi untuk menjadikan medan bawah dan atas tidak menentu untuk memastikan kelas selamat untuk benang; penyegerakan masih diperlukan. Jika tidak, lambat laun anda akan bernasib malang dan dua utas yang melakukan setLower() dan setUpper() dengan nilai yang tidak sesuai boleh membawa julat kepada keadaan yang tidak konsisten. Sebagai contoh, jika nilai awal ialah (0, 5), benang A memanggil setLower(4), dan pada masa yang sama benang B memanggil setUpper(3), operasi berjalin ini akan mengakibatkan ralat, walaupun kedua-duanya akan lulus semakan yang sepatutnya melindungi invarian. Akibatnya, julat akan menjadi (4, 3) - nilai yang salah. Kita perlu menjadikan setLower() dan setUpper() atom kepada operasi julat lain - dan membuat medan tidak menentu tidak akan melakukannya.
Pertimbangan Prestasi
Sebab pertama untuk menggunakan tidak menentu ialah kesederhanaan. Dalam sesetengah situasi, menggunakan pembolehubah sedemikian adalah lebih mudah daripada menggunakan kunci yang dikaitkan dengannya. Sebab kedua ialah prestasi, kadangkala tidak menentu akan berfungsi lebih cepat daripada kunci. Amat sukar untuk membuat kenyataan yang tepat dan merangkumi semua seperti "X sentiasa lebih pantas daripada Y," terutamanya apabila ia berkaitan dengan operasi dalaman Mesin Maya Java. (Sebagai contoh, JVM mungkin melepaskan kunci sepenuhnya dalam beberapa situasi, menjadikannya sukar untuk membincangkan kos yang tidak menentu berbanding penyegerakan secara abstrak). Walau bagaimanapun, pada kebanyakan seni bina pemproses moden, kos membaca tidak menentu tidak jauh berbeza daripada kos membaca pembolehubah biasa. Kos penulisan tidak menentu adalah jauh lebih tinggi daripada menulis pembolehubah biasa disebabkan oleh pagar memori yang diperlukan untuk keterlihatan, tetapi secara amnya lebih murah daripada menetapkan kunci.
Corak untuk penggunaan yang betul tidak menentu
Ramai pakar konkurensi cenderung untuk mengelak daripada menggunakan pembolehubah yang tidak menentu sama sekali kerana ia lebih sukar untuk digunakan dengan betul daripada kunci. Walau bagaimanapun, terdapat beberapa corak yang jelas yang, jika diikuti dengan teliti, boleh digunakan dengan selamat dalam pelbagai situasi. Sentiasa hormati batasan yang tidak menentu - hanya gunakan yang tidak menentu yang bebas daripada apa-apa lagi dalam program, dan ini sepatutnya menghalang anda daripada memasuki wilayah berbahaya dengan corak ini.
Corak #1: Bendera Status
Mungkin penggunaan kanonik pembolehubah boleh ubah ialah bendera status boolean mudah yang menunjukkan bahawa peristiwa kitaran hayat satu kali yang penting telah berlaku, seperti penyiapan permulaan atau permintaan penutupan. Banyak aplikasi termasuk binaan kawalan borang: "sehingga kami bersedia untuk menutup, teruskan berjalan" seperti yang ditunjukkan dalam Penyenaraian 2: Kemungkinan volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff } } kaedah shutdown() akan dipanggil dari suatu tempat di luar gelung - pada utas lain - jadi penyegerakan diperlukan untuk memastikan penutupan keterlihatan pembolehubah yang betulDiminta. (Ia boleh dipanggil daripada pendengar JMX, pendengar tindakan dalam urutan acara GUI, melalui RMI, melalui perkhidmatan web, dll.). Walau bagaimanapun, gelung dengan blok disegerakkan akan menjadi lebih rumit daripada gelung dengan bendera keadaan tidak menentu seperti dalam Penyenaraian 2. Oleh kerana tidak menentu menjadikan kod penulisan lebih mudah dan bendera negeri tidak bergantung pada mana-mana keadaan program lain, ini adalah contoh penggunaan yang baik dari volatile. Ciri bendera status tersebut ialah biasanya hanya terdapat satu peralihan negeri; bendera shutdownRequested bertukar daripada palsu kepada benar, dan kemudian program dimatikan. Corak ini boleh diperluaskan kepada bendera negeri yang boleh berubah ke belakang dan ke belakang, tetapi hanya jika kitaran peralihan (dari palsu kepada benar kepada palsu) berlaku tanpa campur tangan luar. Jika tidak, beberapa jenis mekanisme peralihan atom, seperti pembolehubah atom, diperlukan.
Corak #2: Penerbitan selamat sekali
Ralat keterlihatan yang mungkin berlaku apabila tiada penyegerakan boleh menjadi isu yang lebih sukar apabila menulis rujukan objek dan bukannya nilai primitif. Tanpa penyegerakan, anda boleh melihat nilai semasa untuk rujukan objek yang ditulis oleh utas lain dan masih melihat nilai keadaan lapuk untuk objek itu. (Ancaman ini adalah punca masalah dengan kunci semak dua kali yang terkenal, di mana rujukan objek dibaca tanpa penyegerakan, dan anda berisiko melihat rujukan sebenar tetapi mendapat objek yang dibina separa melaluinya.) Satu cara untuk menerbitkan dengan selamat objek adalah untuk membuat rujukan kepada objek yang tidak menentu. Penyenaraian 3 menunjukkan contoh di mana, semasa permulaan, benang latar belakang memuatkan beberapa data daripada pangkalan data. Kod lain yang mungkin cuba menggunakan data ini menyemak untuk melihat sama ada ia telah diterbitkan sebelum cuba menggunakannya. public class BackgroundFloobleLoader { public volatile Flooble theFlooble; public void initInBackground() { // делаем много всякого theFlooble = new Flooble(); // единственная запись в theFlooble } } public class SomeOtherClass { public void doWork() { while (true) { // чё-то там делаем... // используем theFolooble, но только если она готова if (floobleLoader.theFlooble != null) doSomething(floobleLoader.theFlooble); } } } Jika rujukan kepada Flooble tidak menentu, kod dalam doWork() akan berisiko melihat Flooble yang dibina separa apabila cuba merujuk Flooble. Keperluan utama untuk corak ini ialah objek yang diterbitkan mestilah sama ada selamat untuk benang atau tidak boleh diubah dengan berkesan (tidak berubah dengan berkesan bermakna keadaannya tidak pernah berubah selepas ia diterbitkan). Pautan tidak menentu boleh memastikan objek kelihatan dalam bentuk yang diterbitkan, tetapi jika keadaan objek berubah selepas diterbitkan, penyegerakan tambahan diperlukan.
Corak #3: Pemerhatian Bebas
Satu lagi contoh mudah penggunaan selamat yang tidak menentu ialah apabila pemerhatian secara berkala "diterbitkan" untuk digunakan dalam program. Sebagai contoh, terdapat sensor persekitaran yang mengesan suhu semasa. Benang latar belakang boleh membaca sensor ini setiap beberapa saat dan mengemas kini pembolehubah tidak menentu yang mengandungi suhu semasa. Urutan lain kemudiannya boleh membaca pembolehubah ini, dengan mengetahui bahawa nilai di dalamnya sentiasa terkini. Kegunaan lain untuk corak ini ialah mengumpul statistik tentang program. Penyenaraian 4 menunjukkan bagaimana mekanisme pengesahan boleh mengingati nama pengguna yang terakhir log masuk. Rujukan pengguna terakhir akan digunakan semula untuk menyiarkan nilai untuk digunakan oleh seluruh program. public class UserManager { public volatile String lastUser; public boolean authenticate(String user, String password) { boolean valid = passwordIsValid(user, password); if (valid) { User u = new User(); activeUsers.add(u); lastUser = user; } return valid; } } Corak ini berkembang pada yang sebelumnya; nilai diterbitkan untuk digunakan di tempat lain dalam program, tetapi penerbitan itu bukan acara sekali sahaja, tetapi satu siri acara bebas. Corak ini memerlukan nilai yang diterbitkan tidak boleh berubah dengan berkesan - keadaannya tidak berubah selepas diterbitkan. Kod yang menggunakan nilai mesti sedar bahawa ia boleh berubah pada bila-bila masa.
Corak #4: Corak "kacang meruap".
Corak "kacang meruap" boleh digunakan dalam rangka kerja yang menggunakan JavaBeans sebagai "struktur yang dimuliakan". Corak "kacang meruap" menggunakan JavaBean sebagai bekas untuk sekumpulan sifat bebas dengan pengambil dan/atau penetap. Rasional untuk corak "kacang meruap" ialah banyak rangka kerja menyediakan bekas untuk pemegang data boleh ubah (seperti HttpSession), tetapi objek yang diletakkan dalam bekas ini mestilah selamat untuk benang. Dalam corak kacang yang tidak menentu, semua elemen data JavaBean adalah tidak menentu, dan pengambil dan penetap haruslah remeh - ia tidak seharusnya mengandungi sebarang logik selain daripada mendapatkan atau menetapkan sifat yang sepadan. Selain itu, untuk ahli data yang merupakan rujukan objek, objek tersebut mestilah tidak boleh diubah dengan berkesan. (Ini tidak membenarkan medan rujukan tatasusunan, kerana apabila rujukan tatasusunan diisytiharkan tidak menentu, hanya rujukan itu, dan bukan elemen itu sendiri, mempunyai sifat tidak menentu.) Seperti mana-mana pembolehubah yang tidak menentu, tidak boleh ada invarian atau sekatan yang dikaitkan dengan sifat JavaBeans . Contoh JavaBean yang ditulis menggunakan corak "kacang meruap" ditunjukkan dalam Penyenaraian 5: @ThreadSafe public class Person { private volatile String firstName; private volatile String lastName; private volatile int age; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setAge(int age) { this.age = age; } }
Corak tidak menentu yang lebih kompleks
Corak dalam bahagian sebelumnya merangkumi kebanyakan kes biasa di mana penggunaan tidak menentu adalah munasabah dan jelas. Bahagian ini melihat corak yang lebih kompleks di mana tidak menentu boleh memberikan prestasi atau faedah berskala. Corak tidak menentu yang lebih maju boleh menjadi sangat rapuh. Adalah penting bahawa andaian anda didokumenkan dengan teliti dan corak ini dirangkumkan dengan kuat, kerana walaupun perubahan terkecil boleh memecahkan kod anda! Selain itu, memandangkan sebab utama kes penggunaan yang tidak menentu yang lebih kompleks ialah prestasi, pastikan anda benar-benar mempunyai keperluan yang jelas untuk keuntungan prestasi yang dimaksudkan sebelum menggunakannya. Corak ini ialah kompromi yang mengorbankan kebolehbacaan atau kemudahan penyelenggaraan untuk kemungkinan peningkatan prestasi - jika anda tidak memerlukan peningkatan prestasi (atau tidak dapat membuktikan bahawa anda memerlukannya dengan program pengukuran yang ketat), maka ia mungkin masalah yang buruk kerana itu anda melepaskan sesuatu yang berharga dan mendapat sesuatu yang kurang sebagai balasan.
Corak #5: Kunci baca-tulis yang murah
Sekarang anda harus sedar bahawa tidak menentu terlalu lemah untuk melaksanakan kaunter. Memandangkan ++x pada asasnya ialah pengurangan tiga operasi (baca, tambah, simpan), jika berlaku kesilapan, anda akan kehilangan nilai yang dikemas kini jika berbilang rangkaian cuba menambah pembilang yang tidak menentu pada masa yang sama. Walau bagaimanapun, jika terdapat lebih banyak bacaan daripada perubahan, anda boleh menggabungkan penguncian intrinsik dan pembolehubah tidak menentu untuk mengurangkan overhed laluan kod keseluruhan. Penyenaraian 6 menunjukkan pembilang selamat benang yang menggunakan disegerakkan untuk memastikan operasi kenaikan adalah atom dan menggunakan tidak menentu untuk memastikan hasil semasa kelihatan. Jika kemas kini jarang dilakukan, pendekatan ini boleh meningkatkan prestasi kerana kos bacaan terhad kepada bacaan yang tidak menentu, yang biasanya lebih murah daripada memperoleh kunci yang tidak bercanggah. @ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } public synchronized int increment() { return value++; } } Sebab kaedah ini dipanggil "kunci baca-tulis murah" adalah kerana anda menggunakan mekanisme pemasaan yang berbeza untuk membaca dan menulis. Oleh kerana operasi tulis dalam kes ini melanggar syarat pertama menggunakan volatile, anda tidak boleh menggunakan volatile untuk melaksanakan pembilang dengan selamat - anda mesti menggunakan kunci. Walau bagaimanapun, anda boleh menggunakan tidak menentu untuk menjadikan nilai semasa kelihatan semasa membaca, jadi anda menggunakan kunci untuk semua operasi pengubahsuaian dan tidak menentu untuk operasi baca sahaja. Jika kunci hanya membenarkan satu utas pada satu masa untuk mengakses nilai, bacaan tidak menentu membenarkan lebih daripada satu, jadi apabila anda menggunakan tidak menentu untuk melindungi bacaan, anda mendapat tahap pertukaran yang lebih tinggi berbanding jika anda menggunakan kunci pada semua kod: dan membaca dan merekod. Walau bagaimanapun, ambil perhatian tentang kerapuhan corak ini: dengan dua mekanisme penyegerakan yang bersaing, ia boleh menjadi sangat kompleks jika anda melampaui aplikasi paling asas bagi corak ini.
Ringkasan
Pembolehubah tidak menentu ialah bentuk penyegerakan yang lebih mudah tetapi lebih lemah daripada penguncian, yang dalam sesetengah kes memberikan prestasi atau kebolehskalaan yang lebih baik daripada penguncian intrinsik. Jika anda memenuhi syarat untuk penggunaan selamat meruap - pembolehubah benar-benar bebas daripada kedua-dua pembolehubah lain dan nilai sebelumnya sendiri - kadangkala anda boleh memudahkan kod dengan menggantikan disegerakkan dengan tidak menentu. Walau bagaimanapun, kod yang menggunakan volatile selalunya lebih rapuh daripada kod yang menggunakan penguncian. Corak yang dicadangkan di sini meliputi kes yang paling biasa di mana turun naik adalah alternatif yang munasabah kepada penyegerakan. Dengan mengikuti corak ini - dan berhati-hati untuk tidak menolaknya melebihi had mereka sendiri - anda boleh menggunakan tidak menentu dengan selamat dalam kes di mana ia memberikan faedah.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION