Java Bellek Modeline Giriş

Java Bellek Modeli (JMM), iş parçacıklarının Java çalışma zamanı ortamındaki davranışını açıklar. Bellek modeli, Java dilinin semantiğinin bir parçasıdır ve bir programcının belirli bir Java makinesi için değil, bir bütün olarak Java için yazılım geliştirirken neler bekleyebileceğini ve beklememesi gerektiğini açıklar.

1995 yılında geliştirilen orijinal Java bellek modeli (özellikle "percolocal bellek" anlamına gelir) bir başarısızlık olarak kabul edilir: kod güvenliği garantisini kaybetmeden birçok optimizasyon yapılamaz. Özellikle, çok iş parçacıklı "single" yazmak için birkaç seçenek vardır:

  • ya bir singleton'a erişmenin her eylemi (nesne uzun zaman önce oluşturulduğunda ve hiçbir şey değişemezken bile) bir iş parçacığı arası kilide neden olur;
  • veya belirli koşullar altında, sistem tamamlanmamış bir yalnızlık verir;
  • veya belirli koşullar altında, sistem iki yalnız kişi yaratacaktır;
  • veya tasarım, belirli bir makinenin davranışına bağlı olacaktır.

Bu nedenle hafıza mekanizması yeniden tasarlandı. 2005 yılında Java 5'in piyasaya sürülmesiyle, Java 14'ün piyasaya sürülmesiyle daha da geliştirilen yeni bir yaklaşım sunuldu.

Yeni model üç kurala dayanmaktadır:

Kural 1 : Tek iş parçacıklı programlar sözde sıralı olarak çalışır. Bunun anlamı: gerçekte, işlemci saat başına birkaç işlem gerçekleştirebilir, aynı zamanda sıralarını değiştirebilir, ancak tüm veri bağımlılıkları kalır, bu nedenle davranış sıralıdan farklı değildir.

Kural 2 : Hiçbir yerde yoktan var olan değerler yoktur. Herhangi bir değişkeni okumak (bu kuralın geçerli olmayabileceği geçici olmayan uzun ve çift hariç), varsayılan değeri (sıfır) veya başka bir komut tarafından oraya yazılan bir şeyi döndürür.

Ve 3 numaralı kural : "önce yürütülür" ( önce gerçekleşir ) kesin bir kısmi sıra ilişkisi ile bağlantılıysa, olayların geri kalanı sırayla yürütülür .

daha önce olur

Leslie Lamport Happens kavramını daha önce ortaya atmıştı . Bu, atomik komutlar (++ ve -- atomik değildir) arasında getirilen katı bir kısmi sıra ilişkisidir ve "fiziksel olarak daha önce" anlamına gelmez.

İkinci takımın birinci tarafından yapılan değişikliklerden "bilgi sahibi" olacağını söylüyor.

daha önce olur

Örneğin, bu tür işlemler için biri diğerinden önce yürütülür:

Senkronizasyon ve monitörler:

  • Monitörü yakalama ( kilit yöntemi , senkronize başlatma) ve ondan sonra aynı iş parçacığında ne olursa olsun.
  • Monitörün dönüşü (yöntem kilidini aç , eşitlemenin sonu) ve ondan önce aynı iş parçacığında ne olursa olsun.
  • Monitörü iade etmek ve ardından başka bir iş parçacığı tarafından yakalamak.

Yazmak ve okumak:

  • Herhangi bir değişkene yazma ve ardından aynı akışta okuma.
  • Uçucu değişkene ve yazmanın kendisine yazmadan önce aynı iş parçacığındaki her şey. uçucu okuma ve ondan sonra aynı iş parçacığındaki her şey.
  • Uçucu bir değişkene yazma ve ardından tekrar okuma. Geçici bir yazma, bellekle bir monitör dönüşüyle ​​aynı şekilde etkileşime girerken, okuma bir yakalama gibidir. Görünüşe göre, bir iş parçacığı geçici bir değişkene yazdıysa ve ikincisi onu bulduysa, yazmadan önce gelen her şey, okumadan sonra gelen her şeyden önce yürütülür; resmi görmek.

Nesne bakımı:

  • Statik başlatma ve herhangi bir nesne örneği ile herhangi bir eylem.
  • Yapıcıdaki son alanlara ve yapıcıdan sonraki her şeye yazma. Bir istisna olarak, daha önce olan ilişkisi diğer kurallara geçişli olarak bağlanmaz ve bu nedenle iş parçacıkları arası bir yarışa neden olabilir.
  • Nesne ve finalize() ile herhangi bir çalışma .

Akış hizmeti:

  • Bir iş parçacığını ve iş parçacığındaki herhangi bir kodu başlatmak.
  • İş parçacığıyla ve iş parçacığındaki herhangi bir kodla ilgili değişkenleri sıfırlama.
  • İş parçacığındaki kod ve birleştirme() ; iş parçacığındaki kod ve isAlive() == false .
  • iş parçacığını kesin () ve durduğunu tespit edin.

İş nüanslarından önce olur

Aynı monitör alınmadan önce gerçekleşen bir monitörün serbest bırakılması gerçekleşir. Çıkış değil, sürüm olduğunu belirtmekte fayda var, yani beklemeyi kullanırken güvenlik konusunda endişelenmenize gerek yok.

Bu bilginin örneğimizi düzeltmemize nasıl yardımcı olacağını görelim. Bu durumda, her şey çok basit: sadece harici kontrolü kaldırın ve senkronizasyonu olduğu gibi bırakın. Artık ikinci iş parçacığının tüm değişiklikleri görmesi garanti edilir, çünkü yalnızca diğer iş parçacığı onu serbest bıraktıktan sonra monitörü alacaktır. Ve her şey başlatılana kadar onu serbest bırakmayacağı için, tüm değişiklikleri ayrı ayrı değil, bir kerede göreceğiz:

public class Keeper {
    private Data data = null;

    public Data getData() {
        synchronized(this) {
            if(data == null) {
                data = new Data();
            }
        }

        return data;
    }
}

Uçucu bir değişkene yazma, aynı değişkenden okumadan önce gerçekleşir. Yaptığımız değişiklik elbette hatayı düzeltir, ancak orijinal kodu yazan kişiyi geldiği yere geri koyar - her seferinde bloke eder. Uçucu anahtar kelime kaydedebilir. Aslında söz konusu ifade, volatile ilan edilen her şeyi okurken her zaman gerçek değeri alacağız anlamına gelir.

Ayrıca daha önce de söylediğim gibi uçucu alanlar için yazmak her zaman (uzun ve çift dahil) atomik bir işlemdir. Bir diğer önemli nokta: eğer diğer varlıklara (örneğin bir dizi, Liste veya başka bir sınıf) referansları olan geçici bir varlığınız varsa, o zaman sadece varlığın kendisine yapılan bir referans her zaman "taze" olacaktır, ancak içindeki her şeye değil. geliyor.

Çift kilitleme silindirlerimize geri dönelim. volatile kullanarak durumu şu şekilde düzeltebilirsiniz:

public class Keeper {
    private volatile Data data = null;

    public Data getData() {
        if(data == null) {
            synchronized(this) {
                if(data == null) {
                    data = new Data();
                }
            }
        }
        return data;
    }
}

Burada hala bir kilidimiz var, ancak yalnızca veri == boşsa. Geçici okuma kullanarak kalan vakaları filtreliyoruz. Doğruluk, geçici depolamanın uçucu okumadan önce gerçekleşmesi ve kurucuda meydana gelen tüm işlemlerin, alanın değerini okuyan kişi tarafından görülebilmesi gerçeğiyle sağlanır.