İnternet şirketlerinin yükselişiyle, teknolojimiz için gereksinimler gittikçe artmaktadır.Çoğu zaman şirketler, paradan tasarruf etmek ve makinelerinin performansını en üst düzeye çıkarmak istemektedir, bu da programcıları gerçekten tüketir.
Elbette, toplumun ilerlemesine uyum sağlamak istiyorsanız, programcıların sürekli kendilerini yenilemeleri gerekir, ancak insanlar köklerini unutabilir ve temel bilgileri öğrenebilir.
Hayır, sınıf arkadaşlarımdan biri şikayet etmek için bana geldi İlk iki görüşme sorunsuz geçti ve üç tarafa geçici anahtar kelime yerleştirildi.
Uçucu hakkında konuşalım
Uçucu bir tür değiştiricidir. Volatile işlevi, derleyici optimizasyonu nedeniyle bu talimatın atlanmamasını sağlamak için bir talimat anahtar sözcüğü olarak kullanılacaktır.
Uçucunun özellikleri
Uçucu atomikliğe ilişkin olarak, uçucu bir değişkenin tek bir okuma / yazma işleminin, aşağıdaki SoWhat ve SynSoWhat fonksiyonlarına benzer şekilde, bu tekli okuma / yazma işlemlerini senkronize etmek için aynı kilidi kullandığı kabul edilebilir.
class SoWhat { uçucu int i = 0; // uçucu değiştirilmiş değişken public int getI () { return i; // Tek bir uçucu değişkenin okunması } public void setI (int j) { this.i = j; // tek bir uçucu değişken yazın } public void inc () { i ++; // Birden çok uçucu değişkeni birleştirin } } class SynSoWhat { int i = 0; genel senkronize int getI () { dönüş i; } genel senkronize edilmiş void setI (int j) { this.i = j; } public void inc () {// sıradan yöntem çağrısı int tmp = getI (); // senkronize edilmiş yöntemi çağırın tmp = tmp + 1; // Yaygın yazma yöntemi setI (tmp); // Eşitlenmiş yöntemi çağırın } }Volatile tarafından yazılan bellek semantiği aşağıdaki gibidir:
Uçucu bir değişken yazarken, JMM, iş parçacığına karşılık gelen yereldeki paylaşılan değişkenin değerini ana belleğe temizleyecektir.
public class VolaSemanteme { int a = 0; uçucu boole bayrağı = false; // Nokta budur public void init () { a = 1; bayrak = doğru; // ....... } genel geçersiz kullanım () { if (bayrak) { int i = a * a; } // ....... } }İş Parçacığı A, init yöntemini çağırır ve iş parçacığı B, kullanım yöntemini çağırır.
Uçucu okumanın bellek semantiği aşağıdaki gibidir:
Uçucu bir değişkeni okurken, JMM iş parçacığına karşılık gelen yerel belleği geçersiz kılar. İş parçacığı daha sonra paylaşılan değişkeni ana bellekten okuyacaktır.
public class VolaSemanteme {
int a = 0;
uçucu boole bayrağı = false; // Nokta budur
public void init () {
a = 1;
bayrak = doğru;
// .......
}
genel geçersiz kullanım () {
if (bayrak) {
int i = a * a;
}
// .......
}
}
Akış şeması kabaca şu şekildedir:
Uçucu değişkenlerin bellek görünürlüğü bellek engeline (Bellek Bariyeri) bağlıdır. Bellek engeli ile ilgili özel açıklama daha önce yazılmıştır ve tekrarlanmayacaktır.JMM kurulumu görünmez olmaya zorlanmıştır. Özetle, JMM içinde talimat yeniden düzenlemesi olacak ve talimat yeniden düzenlemesinin doğruluğunu sağlamak için af-if-serial ve-önce-gerçekleşmesi kavramları olacaktır. Bellek bariyeri, 4 montaj düzeyinde anahtar kelimeye dayalı talimatların yeniden düzenlenmesini yasaklar. Uçucu madde için yeniden düzenleme kuralları aşağıdaki gibidir:
JMM'nin geçici için bellek engeli ekleme stratejisi
Her geçici yazma işleminden önce bir StoreStore bariyeri ekleyin. Her geçici yazma işleminden sonra bir StoreLoad bariyeri yerleştirin.
JMM'nin geçici için bellek engeli ekleme stratejisi
Her uçucu okuma işleminden sonra bir LoadLoad bariyeri yerleştirin. Her uçucu okuma işleminden sonra bir LoadStore bariyeri yerleştirin.
Odak noktası, neden bir LoadLoad'un ardından değişken bir okuma olduğudur. Ekleme İki AB iş parçacığı tarafından yürütülecek aşağıdaki koda sahibim ve B iş parçacığının bayrağı aşağıdaki okumayı alır gelişmiş.
Uçucu değişkenlerle değiştirilen paylaşılan değişkenler, işlemleri yazarken CPU tarafından sağlanan Kilit önek talimatını kullanır. CPU seviyesindeki işlevler aşağıdaki gibidir:
Mevcut işlemci önbellek hattının verilerini sistem belleğine geri yaz
Belleğe geri yazma işlemi, diğer CPU'larda aldığınız değişkenlerin geçersiz olduğunu ve bir dahaki sefere kullandığınızda belleği yeniden paylaşmanız gerektiğini söyleyecektir.
Basit kodun ayrıntılı demontajına jitwatch üzerinden bakabiliriz.
package com.sowhat.demo; public class VolaSemanteme { int unvloatileVal = 0; uçucu boole bayrağı = yanlış; public void init () { unvloatileVal = 1; flag = true; // Dokuzuncu satır } genel geçersiz kullanım () { if (bayrak) { int LocalA = unvloatileVal; eğer (LocalA == 0) { yeni RuntimeException ("hata") atar; } } } public static void main (String args) { VolaSemanteme volaSemanteme = new VolaSemanteme (); volaSemanteme.init (); volaSemanteme.use (); } }Sıradan değişkenlere atama işlemleri:
Uçucu değişkenlere atama işlemleri.
Karşılaştırılabilir, uçucu tarafından modifiye edilen değişken gerçekten de bir tane daha kilit eklentisine sahip olacaktır $ 0x0, (% rsp) komutu.
0x0000000114ce95cb: eklentiyi kilitle $ 0x0, (% rsp); * putfield bayrağı ; -com.sowhat.demo.VolaSemanteme :: init @ 7 (satır 9)Uçucu kullanmak için ön koşullar
Uçucu, yalnızca durum programdaki diğer içerikten gerçekten bağımsız olduğunda kullanılabilir.
Durum bayrağı
Belki de uçucu değişkenlerin kanonik kullanımı, başlatmanın tamamlanması veya kapatma talebi gibi önemli bir kerelik olayın gerçekleştiğini belirtmek için basit bir mantıksal durum bayrağının kullanılmasıdır.
volatile boolean shutdownRequested; ...... public void shutdown () {shutdownRequested = true;} public void doWork () { while (! shutdownRequested) { // şeyler yapmak } }Tek seferlik güvenli yayın
Senkronizasyon eksikliği, görünürlüğü imkansız hale getirebilir, bu da orijinal değer yerine bir nesne referansının ne zaman yazılacağını belirlemeyi zorlaştırır. Senkronizasyonun yokluğunda, bir nesne referansının güncellenmiş değeri (başka bir evre tarafından yazılan) ve nesnenin durumunun eski değeri aynı anda var olabilir. (Bu, nesne referanslarının senkronizasyon olmadan okunduğu, iyi bilinen çift işaretli kilitleme sorununun temel nedenidir. Sorun, güncellenmiş bir referans görebilmenizdir, ancak Bu referans aracılığıyla eksik yapılandırılmış nesneleri görün).
public class BackgroundFloobleLoader { halka açık uçucu Flooble theFlooble; public void initInBackground () { // çok şey yap theFlooble = new Flooble (); // bu Flooble'a yazılan tek şeydir } } public class SomeOtherClass { public void doWork () { while (true) { // bir şeyler yapalım ... // Flooble'ı kullanın, ancak yalnızca hazırsa eğer (floobleLoader.theFlooble! = null) doSomething (floobleLoader.theFlooble); } } }Bağımsız gözlem
Uçucunun güvenli kullanımının bir başka basit modu, programda dahili kullanım için düzenli olarak gözlemler yayınlamaktır. Örneğin, ortam sıcaklığını algılayabilen bir ortam sensörü olduğunu varsayalım. Bir arka plan dizisi, sensörü birkaç saniyede bir okuyabilir ve geçerli belgeyi içeren uçucu değişkeni güncelleyebilir. Ardından, diğer ipler bu değişkeni okuyabilir, böylece herhangi bir zamanda en son sıcaklık değerini görebilirler.
public class UserManager { public volatile String lastUser; public boolean authentication (String kullanıcısı, String şifresi) { boolean geçerli = passwordIsValid (kullanıcı, parola); eğer (geçerli) { Kullanıcı u = yeni Kullanıcı (); activeUsers.add (u); lastUser = kullanıcı; } geçerli dönüş; } }uçucu fasulye deseni
Uçucu fasulye modelinde, JavaBean'ın tüm veri üyeleri uçucu tiptedir ve alıcı ve ayarlayıcı yöntemleri çok yaygın olmalıdır - karşılık gelen özellikleri almak veya ayarlamak dışında herhangi bir mantık içeremezler. Ek olarak, bir nesne tarafından referans verilen veri üyeleri için, referans verilen nesnenin etkili bir şekilde değişmez olması gerekir. (Bu, dizi değerlerine sahip öznitelikleri engeller, çünkü bir dizi başvurusu uçucu olarak bildirildiğinde, dizinin kendisi değil, yalnızca başvuru, uçucu anlamlara sahiptir). Herhangi bir uçucu değişken için, değişmezler veya kısıtlar JavaBean özelliklerini içeremez.
@Hayalhanemersin public class Person { özel uçucu String firstName; özel uçucu String lastName; özel değişken int yaş; public String getFirstName () {return firstName;} public String getLastName () {return lastName;} public int getAge () {dönüş yaşı;} public void setFirstName (String firstName) { this.firstName = firstName; } public void setLastName (String lastName) { this.lastName = lastName; } public void setAge (int age) { this.age = yaş; } }Düşük maliyetli okuma-yazma kilidi stratejisi
Uçucunun işlevi bir sayaç uygulamak için yeterli değildir. ++ x aslında üç işlemin basit bir birleşimidir (okuma, ekleme, saklama), birden fazla iş parçacığı aynı anda değişken sayacı artırmaya çalışırsa, güncel değeri kaybolabilir. Okuma işlemleri yazma işlemlerini çok aşarsa, ortak kod yolunun ek yükünü azaltmak için dahili kilitler ve geçici değişkenler birleştirilebilir. Güvenli sayaç, artırma işleminin atomik olmasını sağlamak için senkronize edilmiş kullanır ve mevcut sonucun görünürlüğünü sağlamak için uçucu kullanır. Güncelleme sık değilse, bu yöntem daha iyi performans sağlayabilir, çünkü okuma yolunun maliyeti yalnızca geçici okuma işlemlerini içerir ve bu genellikle çekişmesiz bir kilit edinme maliyetinden daha iyidir.
@Hayalhanemersin public class CheesyCounter { // Ucuz okuma-yazma kilidi hilesi kullanır // Tüm mutatif işlemler 'bu' kilit tutularak YAPILMALIDIR @GuardedBy ("bu") özel uçucu int değeri; public int getValue () {dönüş değeri;} genel senkronize int artışı () { dönüş değeri ++; } }Çifte kontrol
Tekli modun bir uygulaması, ancak birçok kişi uçucu anahtar kelimeyi görmezden gelecektir, çünkü bu anahtar kelime olmadan program da çok iyi çalışabilir, ancak kodun kararlılığı her zaman% 100 olmayabilir, belki gelecekte O anda gizli böcek çıktı.
sınıf Singleton { özel uçucu statik Singleton örneği; public static Singleton getInstance () { eğer (örnek == null) { syschronized (Singleton.class) { eğer (örnek == null) { örnek = new Singleton (); } } } dönüş örneği; } }Geç yüklemenin zarif yolu önerilir: Talep Tutucusunda Başlatma (IODH).
public class Singleton { statik sınıf SingletonHolder { statik Singleton örneği = new Singleton (); } public static Singleton getInstance () { SingletonHolder.instance döndür; } }