Statik değişkenler iş parçacıkları arasında paylaşılıyor mu?


95

İş parçacığı üzerine bir üst düzey Java sınıfındaki öğretmenim emin olmadığım bir şey söyledi.

Aşağıdaki kodun readydeğişkeni mutlaka güncellemeyeceğini belirtti . Ona göre, özellikle her iş parçacığının (ana iş parçacığına karşı ReaderThread) kendi işlemcisi üzerinde çalıştığı ve bu nedenle aynı yazmaçları / önbelleği / vb. Ve bir CPU'yu paylaşmadığı durumda, iki iş parçacığı statik değişkeni paylaşmayabilir. diğerini güncellemeyecek.

Esasen, readyana iş parçacığında güncellenmesinin mümkün olduğunu , ancak içinde DEĞİL ReaderThread, böylece ReaderThreadsonsuz döngüde kalacağını söyledi .

O da programın yazdırmak mümkün olduğunu iddia etti 0ya 42. Nasıl 42basılabileceğini anlıyorum ama değil 0. numberDeğişken varsayılan değere ayarlandığında bunun böyle olacağından bahsetti .

Statik değişkenin iş parçacıkları arasında güncellenmesinin garanti edilmeyeceğini düşündüm, ancak bu Java için bana çok garip geliyor. readyUçucu hale getirmek bu sorunu çözüyor mu ?

Şu kodu gösterdi:

public class NoVisibility {  
    private static boolean ready;  
    private static int number;  
    private static class ReaderThread extends Thread {   
        public void run() {  
            while (!ready)   Thread.yield();  
            System.out.println(number);  
        }  
    }  
    public static void main(String[] args) {  
        new ReaderThread().start();  
        number = 42;  
        ready = true;  
    }  
}

Yerel olmayan değişkenlerin görünürlüğü, statik değişkenler, nesne alanları veya dizi öğeleri olup olmadıklarına bağlı değildir, hepsinde aynı düşünceler vardır. (Dizi öğelerinin uçucu hale getirilememesi sorunu ile.)
Paŭlo Ebermann

1
öğretmeninize '0' görmenin mümkün olduğunu düşündüğü mimarinin ne tür olduğunu sorun. Yine de teoride haklı.
bestsss

4
@bestsss Bu tür bir soru sormak, öğretmene söylediklerinin tüm noktasını kaçırdığını gösterirdi. Önemli olan, yetkin programcıların neyin garanti edildiğini ve neyin garanti edilmediğini anlaması ve en azından neyin garanti edilmediğini ve nedenini tam olarak anlamadan garanti edilmeyen şeylere güvenmemesidir.
David Schwartz

Aynı sınıf yükleyici tarafından yüklenen her şey arasında paylaşılırlar. Konu dahil.
Marquis of Lorne

Öğretmenin (ve kabul edilen cevap)% 100 haklı, ancak bunun nadiren gerçekleştiğinden bahsedeceğim - bu yıllarca saklanacak ve yalnızca en zararlı olduğu zaman kendini gösterecek türden bir sorundur. Sorunu açığa çıkarmaya çalışan kısa testler bile her şey yolundaymış gibi davranma eğilimindedir (Muhtemelen JVM'nin çok fazla optimizasyon yapması için zamanları olmadığı için), bu yüzden farkında olmak gerçekten iyi bir konudur.
Bill K

Yanıtlar:


75

Görünürlük söz konusu olduğunda statik değişkenler hakkında özel bir şey yoktur. Erişilebilirlerse, herhangi bir iş parçacığı onlara ulaşabilir, bu nedenle eşzamanlılık sorunları görme olasılığınız daha yüksektir çünkü daha fazla maruz kalırlar.

JVM'nin bellek modeli tarafından empoze edilen bir görünürlük sorunu var. İşte bellek modelinden ve yazıların iş parçacıkları tarafından nasıl görünür hale geldiğinden bahseden bir makale . Bir iş parçacığının diğer konulara zamanında görünür hale gelmesini sağlayan değişikliklere güvenemezsiniz (aslında JVM'nin bu değişiklikleri herhangi bir zaman diliminde sizin için görünür kılma yükümlülüğü yoktur), önceden gerçekleşen bir ilişki kurmadığınız sürece .

İşte bu bağlantıdan bir alıntı (Jed Wesley-Smith tarafından yapılan yorumda verilmiştir):

Java Dil Spesifikasyonunun 17. Bölümü, paylaşılan değişkenlerin okunması ve yazılması gibi bellek işlemlerinde önceden-olur ilişkisini tanımlar. Bir iş parçacığının yazmasının sonuçlarının başka bir iş parçacığı tarafından okunması, ancak yazma işlemi okuma işleminden önce gerçekleşirse garanti edilir. Senkronize ve geçici yapılar ile Thread.start () ve Thread.join () yöntemleri, önceden gerçekleşenler ilişkileri oluşturabilir. Özellikle:

  • Bir iş parçacığındaki her eylem, daha sonra programın sırasına göre gelen bu iş parçacığındaki her eylemden önce gerçekleşir.

  • Aynı monitörün sonraki her kilitlenmesinden (senkronize blok veya yöntem girişi) önce bir monitörün kilidi (senkronize blok veya yöntem çıkışı) gerçekleşir. Ve önce-olur ilişkisi geçişli olduğu için, bir iş parçacığının kilidi açılmadan önceki tüm eylemleri, bu monitörün herhangi bir iş parçacığı kilitlemesinden sonraki tüm eylemlerden önce gerçekleşir.

  • Uçucu bir alana yazma, aynı alanın sonraki her okunmasından önce gerçekleşir. Uçucu alanların yazılması ve okunması, monitörlere girip çıkarken benzer bellek tutarlılığı etkilerine sahiptir, ancak karşılıklı dışlama kilitlemeyi gerektirmez.

  • Bir iş parçacığı üzerinde başlatma çağrısı, başlatılan iş parçacığındaki herhangi bir eylemden önce gerçekleşir.

  • Bir iş parçacığındaki tüm eylemler, herhangi bir iş parçacığı üzerindeki bir birleşimden başarılı bir şekilde dönmeden önce gerçekleşir.


3
Pratikte, "zamanında" ve "her zaman" eşanlamlıdır. Yukarıdaki kodun hiçbir zaman sona ermemesi çok olasıdır.
TREE

4
Ayrıca bu, başka bir anti-modeli göstermektedir. Birden fazla paylaşılan durumu korumak için uçucu kullanmayın. Burada sayı ve hazır iki durumdur ve her ikisini de tutarlı bir şekilde güncellemek / okumak için gerçek senkronizasyona ihtiyacınız vardır.
TREE

5
Sonunda görünür olmakla ilgili kısım yanlış. Herhangi açık olur-evvel olmadan ilişki herhangi yazma dair bir garanti yoktur hiç JIT, hak bir kayıt içine okuma yutturmak için ve daha sonra herhangi bir güncelleme asla görmeyeceksin oldukça dahilinde olduğu gibi, başka bir iş parçacığı tarafından görülebilir. Olası herhangi bir yük şanstır ve buna güvenilmemelidir.
Jed Wesley-Smith

2
"uçucu anahtar kelimeyi kullanmadığınız veya senkronize etmediğiniz sürece." "Yazar ile okuyucu arasında ilgili bir önceden-gerçekleşmiş ilişki yoksa" ve şu bağlantıyı okumalıdır: download.oracle.com/javase/6/docs/api/java/util/concurrent/…
Jed Wesley-Smith

2
@bestsss iyi görüldü. Maalesef ThreadGroup pek çok şekilde bozulmuştur.
Jed Wesley-Smith

37

Görünürlükten bahsediyordu ve kelimenin tam anlamıyla alınmaması gerekiyordu.

Statik değişkenler gerçekte iş parçacıkları arasında paylaşılır, ancak bir iş parçacığında yapılan değişiklikler başka bir iş parçacığı tarafından hemen görünmeyebilir, bu da değişkenin iki kopyası varmış gibi görünmesine neden olur.

Bu makale, bilgiyi nasıl sunduğu ile tutarlı bir görüş sunmaktadır:

Öncelikle, Java bellek modeli hakkında biraz bilgi sahibi olmalısınız. Yıllar boyunca bunu kısaca ve iyi bir şekilde açıklamak için biraz uğraştım. Bugün itibariyle, onu tarif etmenin en iyi yolu, şu şekilde hayal etmenizdir:

  • Java'daki her iş parçacığı ayrı bir bellek alanında yer alır (bu açıkça doğru değildir, bu yüzden bu konuda benimle kalın).

  • Bir mesaj iletme sisteminde olduğu gibi, bu iş parçacıkları arasında iletişimin olmasını garanti etmek için özel mekanizmalar kullanmanız gerekir.

  • Bir iş parçacığında gerçekleşen bellek yazıları "sızabilir" ve başka bir iş parçacığı tarafından görülebilir, ancak bu hiçbir şekilde garanti edilmez. Açık bir iletişim olmadan, hangi yazıların diğer iş parçacıkları tarafından görüldüğünü veya hatta görülme sırasını garanti edemezsiniz.

...

iplik modeli

Ancak yine, bu sadece JVM'nin tam anlamıyla nasıl çalıştığını değil, iş parçacığı ve değişkenlik hakkında düşünmek için zihinsel bir modeldir.


12

Temelde doğru, ama aslında sorun daha karmaşık. Paylaşılan verilerin görünürlüğü yalnızca CPU önbelleklerinden değil, aynı zamanda talimatların sıra dışı yürütülmesinden de etkilenebilir.

Bu nedenle Java, iş parçacıklarının hangi durumlarda paylaşılan verilerin tutarlı durumunu görebileceğini belirten bir Bellek Modeli tanımlar .

Sizin özel durumunuzda, eklemek volatilegörünürlük sağlar.


8

Elbette, her ikisinin de aynı değişkeni ifade etmeleri anlamında "paylaşılırlar", ancak birbirlerinin güncellemelerini görmeleri gerekmez. Bu, yalnızca statik değil, herhangi bir değişken için geçerlidir.

Ve teoride, değişkenler bildirilmedikçe volatileveya yazmalar açıkça senkronize edilmedikçe, başka bir iş parçacığı tarafından yapılan yazılar farklı bir sırada görünebilir .


5

Tek bir sınıf yükleyicide statik alanlar her zaman paylaşılır. Verileri açık bir şekilde iş parçacıklarıyla kapsamak için, gibi bir tesis kullanmak istersiniz ThreadLocal.


2

Statik temel tür değişkeni başlattığınızda, java varsayılanı statik değişkenler için bir değer atar

public static int i ;

değişkeni bu şekilde tanımladığınızda, i = 0 varsayılan değeri; bu yüzden size 0 elde etme olasılığı vardır, ardından ana iş parçacığı boolean değerini doğruya hazır olarak günceller. ready statik bir değişken olduğundan, ana iş parçacığı ve diğer iş parçacığı aynı bellek adresine başvurur, böylece hazır değişken değişir. böylece ikincil iş parçacığı while döngüsünden ve baskı değerinden çıkar. değer yazdırılırken başlatılan sayı değeri 0'dır. Eğer evre işlemi, ana iş parçacığı güncelleme numarası değişkeninden önce döngü sırasında geçtiyse. o zaman 0 yazdırma imkanı var


-2

@dontocsata öğretmenine geri dönebilir ve ona biraz ders verebilirsin :)

gerçek dünyadan birkaç not ve ne gördüğünüz veya ne söyleneceği önemli değil. Lütfen DİKKAT EDİN, aşağıdaki sözcükler tam olarak gösterilen sırayla bu özel durumla ilgilidir.

Aşağıdaki 2 değişken, hemen hemen her bilinen mimari altında aynı önbellek hattında yer alacaktır.

private static boolean ready;  
private static int number;  

Thread.exit(ana iş parçacığının) çıkması garanti edilir ve exitiplik grubu iplik çıkarma (ve diğer birçok sorun) nedeniyle bir bellek perdesine neden olması garanti edilir. (bu senkronize bir çağrıdır ve senkronizasyon parçası olmadan uygulanacak tek bir yol göremiyorum çünkü hiçbir daemon thread kalmadıysa ThreadGroup'un da sonlanması gerekir, vb.).

Başlatılan iş parçacığı ReaderThread, bir arka plan programı olmadığı için süreci canlı tutacak! Böylece readyve numberbirlikte yıkanacaktır (veya bir bağlam anahtarı oluşursa önceki sayı) ve bu durumda yeniden sıralama için gerçek bir neden yok, en azından bir tane düşünemiyorum bile. Başka bir şey görmek için gerçekten tuhaf bir şeye ihtiyacınız olacak 42. Yine her iki statik değişkenin aynı önbellek satırında olacağını varsayıyorum. 4 bayt uzunluğunda bir önbellek satırı VEYA onları sürekli bir alana (önbellek hattı) atamayacak bir JVM hayal edemiyorum.


3
@bestsss, bugün hepsi doğru olsa da, programın anlambilimine değil, gerçekliği için mevcut JVM uygulamasına ve donanım mimarisine güveniyor. Bu, programın çalışmasına rağmen hala bozuk olduğu anlamına gelir. Bu örneğin, belirtilen şekillerde gerçekten başarısız olan önemsiz bir varyantını bulmak kolaydır.
Jed Wesley-Smith

1
Spesifikasyona uymadığını söyledim, ancak bir öğretmen olarak en azından bazı meta mimarisinde gerçekten başarısız olabilecek uygun bir örnek buldum, bu yüzden örnek biraz gerçek.
bestsss

6
Muhtemelen iş parçacığı için güvenli kod yazma konusunda gördüğüm en kötü tavsiye.
Lawrence Dol

4
@Bestsss: Sorunuzun basit cevabı şudur: "Sisteminizin veya uygulamanızın yan etkilerine değil, spesifikasyona ve belgeye kodlayın". Bu, özellikle altta yatan donanımdan bağımsız olacak şekilde tasarlanmış bir sanal makine platformunda önemlidir.
Lawrence Dol

1
@Bestsss: Öğretmenlerin vurguladığı nokta, (a) kodun test ettiğinizde iyi çalışabileceği ve (b) kodun bozuk olmasının nedeni, spesifikasyonun garantilerine değil donanımın çalışmasına bağlı olmasıdır. Mesele şu ki, sorun yok gibi görünüyor, ama sorun değil.
Lawrence Dol
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.