Röportajda Java'nın çok iş parçacıklı güvenliği soruldu, bu yüzden ona cevap verdim

Java'da çok iş parçacıklı programlama söz konusu olduğunda, iş parçacığı güvenliği atlanmamalıdır. İş parçacığı güvenliği atomiklik, görünürlük ve sıra gibi özellikleri içerir. Bugün, aralarındaki bağlantıya ve gerçekleştirme ilkesine bir göz atalım.

Konular ve yarış koşulları

Geliştirilen uygulama bir süreç içinde çalışacaktır, diğer bir deyişle süreç, programın çalışan örneğidir. Bir Java programını çalıştırmanın özü, bir Java sanal makine sürecini çalıştırmaktır.

Bir işlem birden fazla iş parçacığı içerebilirse ve bu iş parçacıkları işlemdeki kaynakları paylaşır. Herhangi bir kod parçası tek bir iş parçacığında çalışacak ve aynı zamanda birden çok iş parçacığında çalışacaktır. Bir iş parçacığı tarafından tamamlanacak hesaplamalara görevler denir.

Programın verimliliğini artırmak için, birlikte çalışmak için birden fazla görev oluşturacağız.Bu çalışma modu paralel olabilir.A görevi yürütülürken, B görevi de yürütülür.

Yürütme sırasında birden fazla görev (iş parçacığı) aynı kaynak (değişken) üzerinde çalışıyorsa, bu kaynağa (değişken) paylaşılan kaynak (değişken) denir.

Birden çok iş parçacığı aynı anda paylaşılan kaynaklar üzerinde çalıştığında, örneğin: kaynak yazma, çalıştırılan kaynakların farklı zamanlarda farklı sonuçlar görmesine neden olacak bir yarış koşulu olacaktır.

Aşağıdaki gibi aynı kaynağa / değişkene erişen birden çok iş parçacığı örneğini görelim:

A ve B iş parçacıkları, iki iş parçacığı arasında nasıl geçiş yapılacağını bilmeden aynı anda Counter nesnesinde add () yöntemini çalıştırdığında, JVM kodu aşağıdaki sırayla çalıştıracaktır:

  • This.count değerini bellekten alın ve register'a koyun.
  • Kayıt defterindeki değeri değer kadar artırın.
  • Kayıt defterindeki değeri belleğe geri yazın.

Yukarıdaki işlemler A ve B evreleri arasına eklendiğinde, aşağıdaki durumlar ortaya çıkacaktır:

İki iş parçacığı, count değişkenine sırasıyla 2 ve 3 ekler İstenen sonuç, iki iş parçacığının yürütülmesinden sonra count değerinin 5'e eşit olmasıdır.

Bununla birlikte, iki iş parçacığı tarafından bellekten okunan başlangıç değeri 0 olsa bile, sırayla 2 ve 3 eklenir ve sırasıyla belleğe geri yazılır.

Bununla birlikte, son değer beklenen 5 değil, belleğe en son yazılan iş parçacığının (A iş parçacığı) değeridir (3).

Hafızaya son yazma A evresi olduğundan sonuç 3'tür, ancak B evresinin belleğe en son yazması da mümkündür, bu nedenle sonuç bilinemez.

Bu nedenle, senkronizasyon mekanizması kullanılmazsa, iş parçacıkları arasındaki çapraz yazma kaynakları / değişkenleri kontrol edilemez olacaktır.

Bir hesaplama sonucunun doğruluğunun zamanla ilişkili olduğu bu fenomeni Yarış Koşulu olarak adlandırıyoruz.

İş parçacığı güvenli

Daha önce, birden çok iş parçacığının aynı anda bir kaynak / değişken yazdığı yarış koşullarından bahsetmiştik. Bu durumun ortaya çıkması nihai sonuçta belirsizliğe neden olacaktır.

Yazılı kaynak Java'da bir sınıf olarak kabul edilirse, bu sınıf iş parçacığı açısından güvenli değildir.

Bu sınıf tek iş parçacıklı bir ortamda normal olarak çalışsa bile, çok iş parçacıklı bir ortamda normal şekilde çalışamaz. Örneğin: ArrayList, HashMap, SimpledateFormat.

Ardından, iplik güvenliğini sağlamak için aşağıdaki üç hususu dikkate almanız gerekir, yani:

  • Atomiklik
  • Görünürlük
  • Düzenlilik

Atomiklik

Atomiklik, belirli bir işlemin veya birden fazla işlemin tamamının yürütüldüğü veya yürütülmediği ve hiçbir ara işlemin gerçekleşmeyeceği anlamına gelir. Aynı durum iş parçacığı yürütmesine geçerken de geçerlidir Evre içinde yürütülen işlemler ya yürütülmez ya da hepsi yürütülür.

Örneğin, Java'da temel veri türü okuma işlemi atomik bir işlemdir, aşağıdaki ifadeye bakın:

  • x = 10
  • x = x + 1

İlk cümle atomik bir işlemdir, çünkü 10 değerini doğrudan x'e atar ve evre bu cümleyi yürüttüğünde, doğrudan belleğe 10 yazar.

İkinci cümle üç işlem içerir, x'in değerini okuyun, 1 işlem ekleyin ve yeni bir değer yazın. Bu üç işlem birlikte atomik işlemler değildir.

Java'daki atomlar şunları içerir:

  • kilit
  • Kilidini aç
  • okumak
  • yük
  • kullanım
  • atama (atama)
  • mağaza
  • yazmak

Farz edelim ki A ve B ifadeleri 2'yi birlikte çalıştırıyor ve x değişkenine aynı anda yazıyor.Atomik işlem tatmin olmadığından, elde edilen sonuç da belirsiz. Java'da atomikliğe ulaşmanın iki yolu vardır. Biri kilit kullanmaktır (Kilit).

Kilit özeldir. Birden fazla iş parçacığı tarafından erişildiğinde, paylaşılan bir değişkene herhangi bir zamanda bir iş parçacığı tarafından erişilebileceğini garanti edebilir, bu da yarış koşulları olasılığını ortadan kaldırır.

Diğeri, doğrudan donanım (işlemci ve bellek) katmanında uygulanan ve "donanım kilidi" olarak adlandırılan, işlemci tarafından sağlanan CAS (Karşılaştır ve Değiştir) talimatını kullanarak gerçekleştirilir.

Görünürlük

Atomiklik hakkında konuştuktan sonra, görünürlükten bahsedelim. Adından da anlaşılacağı gibi, çok iş parçacıklı erişimde, bir iş parçacığı paylaşılan bir değişkeni güncelledikten sonra, sonraki erişim iş parçacığı güncellenen sonucu hemen okuyabilir.

Bu duruma görünürlük denir ve paylaşılan değişkenlerde yapılan güncellemeler diğer iş parçacıkları tarafından görülebilir, aksi takdirde görünmez olarak adlandırılır.

Java'nın nasıl görünürlük elde ettiğini görelim. İlk olarak, A evresinin bir komut yürüttüğünü ve bu komutun CPU A'da çalıştığını varsayalım.

Bu komutla yazılan paylaşılan değişken, CPU A'nın kaydında saklanacaktır. Komut yürütüldüğünde, paylaşılan değişken kayıttan belleğe yazılacaktır.

Not: Aslında, kayıt yoluyla önbelleğe, ardından yazma arabelleğine ve düzensiz kuyruğa ve son olarak da belleğe.

Örnek olarak, basitleştirilmiş bir ifade kullanılmıştır. Bunun amacı, paylaşılan değişkenlere başka bir CPU'daki iş parçacıkları tarafından erişilmesine izin vermektir.

Bu nedenle, paylaşılan değişkenleri belleğe yazma eylemine "işlemci önbelleğini temizleme" denir. Yani, paylaşılan değişkenleri işlemci önbelleğinden belleğe boşaltın.

İşlemci önbelleğini temizleyin

Şu anda, B iş parçacığı B CPU üzerinde çalışıyor olur. Paylaşılan değişkenleri elde etmek için, komutun bellekteki paylaşılan değişkenlerden senkronize edilmesi gerekir.

Bu önbellek eşitleme işlemine "işlemci önbelleğini temizleme" denir. Yani, önbelleği bellekten işlemcinin kayıtlarına boşaltın.

Bu iki adımdan sonra, CPU B üzerinde çalışan iş parçacığı, CPU A üzerindeki iş parçacığı tarafından işlenen paylaşılan değişkenle eşitlenebilir. Aynı zamanda paylaşılan değişkenlerin görünürlüğünü de garanti eder.

İşlemci önbelleğini temizleyin

Düzenlilik

Görünürlük hakkında konuştuktan sonra siparişten bahsedelim. Java derleyicisi kod yürütme sırasını ayarlayacaktır.

İki işlemin yürütme sırasını değiştirmek mümkündür.Başka bir işlemcide gerçekleştirilen birden çok işlem için, diğer işlemciler açısından, talimatların yürütme sırası da tutarsız olabilir.

Java bellek modelinde, derleyici ve işlemcinin talimatları yeniden sıralamasına izin verilir, ancak yeniden sıralama süreci tek iş parçacıklı programların yürütülmesini etkilemeyecektir, ancak birden çok iş parçacığının eşzamanlı yürütülmesinin doğruluğunu etkileyecektir.

Bunun nedeni, performansla ilgili hususlar için, derleyicinin programın doğruluğunu etkilemeden kaynak kod sırasını ayarlamasıdır (tek iş parçacıklı program).

Java'da "düzenliliği" sağlamak için Volatile anahtar sözcüğünü kullanabilirsiniz. Sırayı sağlamak için Senkronize ve Kilit'i de kullanabilirsiniz. Daha sonra, bellek engelleri üzerinden sipariş vermeye başlayacağız.

Çok iş parçacıklı senkronizasyon ve kilit

Daha önce bahsedilen iş parçacığı yarışı ve iş parçacığı güvenliği, paylaşılan değişkenlere çok iş parçacıklı erişim etrafında tartışılmıştır. Tam da bu durum nedeniyle, çok iş parçacıklı geliştirme geliştirilirken bu sorunun çözülmesi gerekiyor.

İş parçacığı güvenliğini sağlamak için, birden çok iş parçacığının eşzamanlı erişimi seri erişime dönüştürülecektir. Lock, çok iş parçacıklı senkronizasyonu sağlamak için bu fikri kullanır.

Kilit, paylaşılan veri erişimi için bir lisans gibidir. Herhangi bir iş parçacığının paylaşılan verilere erişmeden önce bu kilidi alması gerekir.

Bir iş parçacığı bir kilit aldığında, kilit için geçerli olan diğer iş parçacıklarının beklemesi gerekir. Kilidi alan iş parçacığı, iş parçacığı üzerindeki görevlere göre kodu yürütecek ve kod yürütülene kadar kilit serbest bırakılmayacaktır.

Kilidin alınması ile kilidin serbest bırakılması arasında yürütülen kod alanına kritik bölüm, kritik bölümde erişilen verilere ise paylaşılan veriler denir.

İş parçacığı kilidi serbest bıraktıktan sonra, diğer iş parçacıkları kilidi elde edebilir ve ardından paylaşılan veriler üzerinde çalışabilir.

Kritik bölüme çok iş parçacıklı erişim, paylaşılan verilere erişim

Yukarıda açıklanan işlemler aynı zamanda birbirini dışlayan işlemler olarak da adlandırılır ve kilitler, bu birbirini dışlayan işlem aracılığıyla yarış koşullarının atomikliğini garanti eder.

Daha önce bahsedilen atomikliği hatırlıyor musunuz? Paylaşılan verilerdeki bir veya daha fazla işlem tamamlandı veya tamamlanmadı ve hiçbir ara durum görünmüyor.

Kritik bölümdeki kodun atomik olmadığını varsayın. Örneğin, yukarıda bahsedilen "x = x + 1" üç işlemi içerir, x'in değerini okuyun, 1 ekleme işlemini gerçekleştirin ve yeni bir değer yazın.

Birden çok iş parçacığında erişilirse, çalışma süresine bağlı olarak farklı sonuçlar elde edilecektir. Bu işleme bir kilit eklerseniz, onu "atomik" yapabilirsiniz, yani bir iş parçacığı ona eriştiğinde, diğer evreler ona erişemez.

Çok iş parçacıklı geliştirmede kilitlerin önemi hakkında konuştuktan sonra, Java'daki kilit türlerine bir göz atalım.

Dahili kilit

Dahili kilitler, yöntemleri değiştirmek için Senkronize anahtar sözcüğünü kullanan ve kritik bölümler oluşturmak için kod bloklarını kullanan monitörler olarak da adlandırılır.

Eşitlenmiş anahtar sözcük, eşitlenmiş yöntemleri, eşitlenmiş statik yöntemleri, eşitlenmiş örnek yöntemlerini ve eşitlenmiş kod bloklarını değiştirmek için kullanılabilir.

Senkronize tarafından başlatılan kod bloğu, yukarıda bahsedilen kritik bölümdür. Kilit tutamacı bir nesneye bir referanstır.Örneğin, mevcut nesneyi belirtmek için this anahtar sözcüğü olarak yazılabilir.

Kilit koluna karşılık gelen monitör, karşılık gelen senkronizasyon bloğunun kılavuz kilidi olarak adlandırılır ve karşılık gelen senkronizasyon bloğuna, kilit tarafından yönlendirilen senkronizasyon bloğu adını veririz.

Dahili kilit şeması

Kilit kolu genellikle sonlandırılır (özel son). Çünkü kilit kolu değiştiğinde, aynı kod bloğunun birden çok iş parçacığı farklı kilitler kullanır ve bu da yarış koşullarına yol açar.

Senkronize statik yöntem, geçerli sınıfı önyükleme kilidi olan senkronize edilmiş bloğa eşdeğerdir. Bir iş parçacığı kritik bir bölümde kodu yürüttüğünde, kritik bölümün önyükleme kilidini tutmalıdır.

Kritik bölüm kodu yürütüldüğünde, kritik bölümü yönlendiren kilit serbest bırakılacaktır. Dahili kilit uygulaması ve bırakma işlemi Java sanal makinesi tarafından tamamlanır.

Bu nedenle, Senkronize tarafından uygulanan kilitlere dahili kilitler denir. Bu nedenle, kilit sızmaz.Java derleyicisi kod bloğunu bayt koduna derlediğinde, kritik bölüm tarafından atılan istisnayı işler.

Java sanal makinesi, her dahili kilide, kilidi almayı bekleyen iş parçacıklarını kaydetmek için kullanılan bir giriş kümesi atar. Kilit için uygulanamayan iş parçacığı, giriş kümesinde kilit için tekrar başvurma fırsatı için bekleyecektir.

Bekleme kilidi diğer iş parçacıkları tarafından serbest bırakıldığında, giriş kümesindeki bekleyen iş parçacığı uyanacak ve kilide başvurma fırsatı elde edilecektir.

Dahili kilit mekanizması, bekleyen iş parçacıkları arasından seçilecektir.Seçim kuralları, iş parçacığı etkinliğine ve önceliğe dayalı olacaktır.Seçilen iş parçacığı, sonraki işlemler için kilidi tutacaktır.

Kilidi göster

Ekran kilidi, JDK 1.5'te tanıtılan özel bir kilittir.İş parçacığı senkronizasyon mekanizması olarak mevcuttur.İşlevi dahili kilitle aynıdır, ancak dahili kilidin sahip olmadığı bazı özellikler sağlar. Görüntü kilidi, java.util.concurrent.locks.Lock arabiriminin bir örneğidir.

Ekran kilidi gerçekleştirme adımları şunlardır:

  • Kilit arayüzünün bir örneğini oluşturun
  • Ekran kilidi kilidi isteyin
  • Paylaşılan verilere erişim
  • Kilit sızıntısını önlemek için sonunda kilidi açın

Kilit kullanımı örnek diyagramını göster

Ekran kilidi hem adil olmayan hem de adil kilitleri destekler. Adil kilitlemede, iş parçacıkları sıkı ilk giren ilk çıkar (FIFO) sırasına göre kilit kaynaklarını alır.

Paylaşılan değişkenleri elde etmesi gereken bir "geçerli iş parçacığı" varsa, sıraya alınması gerekir. Kilit serbest bırakıldığında, kuyruktaki ilk iş parçacığı (Düğüm1) tarafından alınır ve bu böyle devam eder.

Adil kilidin şematik diyagramı

Haksız bir kilitlemede, bir iş parçacığı kilidi serbest bıraktığında, "geçerli iş parçacığı", kilit kaynakları için bekleme kuyruğundaki ilk iş parçacığı (Düğüm1) ile rekabet eder. Hangi iş parçacığının kilit kaynaklarını tuttuğunu iş parçacığı etkinliğine ve önceliğine göre belirleyin.

Haksız kilit şeması

Adil kilitler, kilit zamanlamasının adil olmasını sağlar, ancak iş parçacığı askıya alma ve uyanma olasılığını artırır, yani bağlam değiştirme maliyetini artırır. Haksız kilitler, daha iyi performansa sahip olacak ve daha yüksek verim sağlayabilecek bir rekabet mekanizması ekledi.

Elbette haksız kilitler, kilidi elde etme süresini daha belirsiz hale getirir ve bu da engelleme kuyruğundaki iş parçacıklarının uzun süre aç kalmasına neden olabilir.

İş parçacığı senkronizasyon mekanizması: bellek engeli

Paylaşılan değişkenlere çok iş parçacıklı erişimden, yarış koşullarının varlığından bahsetmişken ve ardından bu sorunu çözmek için bir kilit mekanizması tanıtın.

Yukarıda bahsedilen dahili kilitler ve ekran kilitleri, iplik senkronizasyonu problemini çözmek için ve yarış koşullarında "atomisite" problemini çözdüğünden bahsetmiştir.

Bundan sonra, "görünürlük" ve "düzen" e nasıl ulaşılacağını anlamak için bellek bariyer mekanizmasını tanıtarak.

İşte bellek engeli kavramı: Yürütme için iki CPU talimatı arasına bellek engeli yerleştirilmiştir. Düzen ve görünürlüğü sağlamak için derleyici ve işlemcinin yeniden sıralanmasını engellemek için kullanılır.

Görünürlük için, bir iş parçacığı bir kilit aldığında ve serbest bıraktığında gerçekleştirilen iki eylemden bahsettik: "işlemci önbelleğini temizle" ve "işlemci önbelleğini temizle".

İlk eylem, kilidi tutan ipliğin paylaşılan değişkeni okumasını garanti eder ve ikinci eylem, kilidi tutan ipliğin, paylaşılan değişken güncellendikten sonra sonraki iş parçacıkları tarafından görülebileceğini garanti eder.

Ayrıca engelin etkisini elde etmek için işlemcinin değeri yazıp okumadan önce ana belleğin değerini önbelleğe yazmasına ve görünürlük sağlamak için geçersiz kuyruğu temizlemesine neden olacaktır.

Düzen için. Açıklamak için bir örnek alalım, bir dizi CPU talimatı olduğunu varsayalım:

  • Mağaza, "mağaza talimatı" anlamına gelir
  • Yük, "komutu oku" anlamına gelir
  • StoreLoad, "yazma ve okuma bellek engeli" anlamına gelir

MağazaYükleme bellek bariyer diyagramı

StoreLoad bariyerinden önceki Store komutu, StoreLoad bariyerinden sonra, yani yeniden sıralama ile Load komutu ile pozisyon değiştiremez.

Ancak StoreLoad bariyerinden önceki ve sonraki talimatlar değiştirilebilir, yani Store1 Store2 ile değiştirilebilir ve Load2 Load3 ile değiştirilebilir.

4 ortak engel vardır:

  • LoadLoad bariyeri: Talimatların sırası şu şekildedir: Yükle1 Yük Yük Yükle2. Load1 komutunun Load2 ve sonraki komutlar çalıştırılmadan önce tamamlandığından emin olmak gerekir.
  • StoreStore bariyeri: Talimatların sırası şu şekildedir: Mağaza1 MağazaStore Mağaza2. Store2 ve sonraki talimatları yürütmek için Store1 talimatlarının diğer işlemciler tarafından görülebildiğinden emin olmak gerekir.
  • LoadStore bariyeri: Talimatların sırası aşağıdaki gibidir: Load1 LoadStore Store2. Store2'den önce Load1 komutunun yürütülmesinin tamamlandığından ve sonraki komutların yürütülebildiğinden emin olmak gerekir.
  • Mağaza Yük bariyeri: Talimat dizisi aşağıdaki gibidir: Store1 StoreLoad Load2. Store1 komutunun, Load2 ve sonraki komutlar çalıştırılmadan önce tüm işlemciler tarafından görülebilir olduğundan emin olmak gerekir. Bu bellek engelinin ek yükü, dört engelden en büyüğüdür (yazma arabelleğini temizlemek, işlemci önbelleğini temizlemek). Aynı zamanda, diğer üç bellek engelinin işlevlerini birleştiren evrensel bir engeldir.

Genellikle, Uçucu ve Eşitlenmiş anahtar sözcükler Java'da bellek engellerini uygulamak için yaygın olarak kullanılır ve Güvenli Olmayan aracılığıyla da uygulanabilir.

sonuç olarak

İş parçacığı tanımından ve paylaşılan değişkenlere çok iş parçacıklı erişimden başlayarak, kaynaklar için iş parçacığı rekabeti olgusu yaşanmıştır. Yarış koşulları, kaynaklara çok iş parçacıklı erişim sağlayacak, zaman geçtikçe kaynakların sonuçları kontrol edilemez.

Bu, çok iş parçacıklı programlamamız için bir zorluk oluşturmaktadır. Bu nedenle, iplik güvenliği sorununu atomiklik, görünürlük ve düzen yoluyla çözmemiz gerekiyor.

Paylaşılan kaynakların senkronizasyonu bu sorunları çözebilir, Java, çözüm olarak en iyi dahili kilit ve ekran kilidi uygulamasını sağlar.

Sonunda, iş parçacığı senkronizasyonunun altında yatan mekanizmayı tanıtır: bellek engeli. CPU talimatlarının yeniden sıralanmasını organize ederek görünürlük ve düzen sorunlarını çözer.

Projenizde Java koleksiyonlarını kullanırken kaçınmanız gereken bazı tuzaklar
önceki
Bu makale, Java eşzamanlılığında kilit optimizasyonunu ve iş parçacığı havuzu optimizasyonunu anlamanızı sağlar
Sonraki
Nginx'in tüm sihirli işlevlerine sahip misiniz?
Sıfırdan yeni başlayanlar için bir arka uç teknoloji yığını oluşturun
RocketMQ işlem mekanizmasının uygulama süreci, mesaj göndermede neden sıfır kayıp elde edebiliyor?
SpringBoot projesi: RedisTemplate hafif mesaj kuyruğu uygular
Birden fazla deneyin paralel yinelemesini nasıl elde edebilirsiniz, Alimama'nın A / B testi uygulaması hakkında konuşun
On milyarlarca trafik taşıyan yüksek performanslı bir mimari nasıl tasarlanır?
Ant Financial'ın 100 milyon düzeyinde eşzamanlılık altında mobil uçtan uca ağ erişim mimarisinin analizi
Zookeeper tarafından dağıtılmış kilit ve Zookeeper'a dayalı liderlik seçimi hakkında derinlemesine anlayış
Kandırılmaktan bunalıma giren sarhoş bir adam yüz dolarlık banknot dağıtır ve intihar etmek ister.
Tayland'da okumak için sahip olmanız gereken yetenekleri biliyor musunuz?
Lisansüstü eğitim için Tayland'a gitmek ister misiniz, bunların hepsini biliyor musunuz?
iyi haberler! Tayland'da hastaneden taburcu edilen iki önemli hasta: Tayland, bulaşıcı hastalıkları önleme ve kontrol etme kabiliyetiyle dünyada altıncı sırada
To Top