CodeGym /Java Blogu /Rastgele /Konuları yönetme. uçucu anahtar kelime ve verim() yöntemi...
John Squirrels
Seviye
San Francisco

Konuları yönetme. uçucu anahtar kelime ve verim() yöntemi

grupta yayınlandı
MERHABA! Multithreading çalışmamıza devam ediyoruz. volatileBugün anahtar kelimeyi ve yöntemi öğreneceğiz yield(). Hadi dalalım :)

uçucu anahtar kelime

Çok iş parçacıklı uygulamalar oluştururken iki ciddi sorunla karşılaşabiliriz. Birincisi, çok iş parçacıklı bir uygulama çalışırken, farklı iş parçacıkları değişkenlerin değerlerini önbelleğe alabilir (bu konudan daha önce 'Using volatile' başlıklı derste bahsetmiştik ). Bir iş parçacığının bir değişkenin değerini değiştirdiği, ancak ikinci iş parçacığının değişkenin önbelleğe alınmış kopyasıyla çalıştığı için değişikliği görmediği bir duruma sahip olabilirsiniz . Doğal olarak, sonuçlar ciddi olabilir. Diyelim ki bu sadece herhangi bir eski değişken değil, birdenbire rasgele yukarı ve aşağı zıplamaya başlayan banka hesap bakiyeniz :) Bu kulağa eğlenceli gelmiyor, değil mi? İkincisi, Java'da tüm ilkel türleri okuma ve yazma işlemleri,longdouble, atomiktir. Örneğin, bir iş parçacığındaki bir değişkenin değerini değiştirirseniz intve başka bir iş parçacığında değişkenin değerini okursanız, ya eski değerini alırsınız ya da yenisini, yani değişiklikten kaynaklanan değeri alırsınız. iş parçacığında 1. "Ara değerler" yoktur. longAncak, bu s ve s ile çalışmaz double. Neden? Platformlar arası destek nedeniyle. Başlangıç ​​seviyelerinde Java'nın yol gösterici ilkesinin 'bir kez yaz, her yerde çalıştır' olduğunu söylediğimizi hatırlıyor musunuz? Bu, platformlar arası destek anlamına gelir. Başka bir deyişle, bir Java uygulaması her türlü farklı platformda çalışır. Örneğin, Windows işletim sistemlerinde, Linux veya MacOS'un farklı sürümleri. Hepsinde aksamadan çalışacaktır. 64 bit tartım,longdoubleJava'daki 'en ağır' ilkel öğelerdir. Ve bazı 32-bit platformlar, 64-bit değişkenlerin atomik okumasını ve yazılmasını uygulamazlar. Bu tür değişkenler iki işlemde okunur ve yazılır. Önce değişkene ilk 32 bit yazılır ve ardından 32 bit daha yazılır. Sonuç olarak, bir sorun ortaya çıkabilir. Bir iş parçacığı, bir değişkene 64 bitlik bir değer yazar Xve bunu iki işlemde yapar. Aynı zamanda, ikinci bir iş parçacığı değişkenin değerini okumaya çalışır ve bunu iki işlem arasında yapar - ilk 32 bit yazılırken ikinci 32 bit yazılmaz. Sonuç olarak, orta, yanlış bir değer okuyor ve bir hatamız var. Örneğin böyle bir platformda 9223372036854775809 numarasına yazmaya çalışırsak bir değişkene, 64 bit kaplar. İkili biçimde, şöyle görünür: 100000000000000000000000000000000000000000000000000000000001 İlk iş parçacığı sayıyı değişkene yazmaya başlar. İlk başta, ilk 32 biti (1000000000000000000000000000000) ve ardından ikinci 32 biti (000000000000000000000000000001) yazar. Ve ikinci iş parçacığı, değişkenin halihazırda yazılmış olan ilk 32 bit olan ara değerini (10000000000000000000000000000000) okuyarak bu işlemler arasında sıkışabilir. Ondalık sistemde bu sayı 2.147.483.648'dir. Yani sadece 9223372036854775809 sayısını bir değişkene yazmak istedik ama bu işlem bazı platformlarda atomik olmadığı için elimizde birdenbire ortaya çıkan ve bilinmeyen bir etkisi olacak olan 2,147,483,648 numaralı kötü numaramız var. programı. İkinci iş parçacığı, yazılması bitmeden önce basitçe değişkenin değerini okudu, yani iş parçacığı ilk 32 biti gördü, ancak ikinci 32 biti görmedi. Elbette bu sorunlar dün ortaya çıkmadı. Java bunları tek bir anahtar kelime ile çözer: volatile. Eğer kullanırsakvolatileprogramımızda bazı değişkenleri bildirirken anahtar kelime…

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…Bu demektir:
  1. Her zaman atomik olarak okunacak ve yazılacaktır. 64 bit doubleveya long.
  2. Java makinesi onu önbelleğe almaz. Böylece 10 iş parçacığının kendi yerel kopyalarıyla çalıştığı bir durumla karşılaşmazsınız.
Böylece tek kelime ile çok ciddi iki sorun çözülmüş olur :)

verim() yöntemi

Sınıfın birçok yöntemini zaten inceledik Thread, ancak sizin için yeni olacak önemli bir yöntem var. yield()Yöntem bu . Ve tam olarak adının ima ettiği şeyi yapar! Konuları yönetme.  uçucu anahtar kelime ve verim() yöntemi - 2Yöntemi bir iş parçacığında çağırdığımızda yield, aslında diğer iş parçacıklarıyla konuşur: 'Hey millet. Herhangi bir yere gitmek için özellikle acelem yok, bu yüzden herhangi birinizin işlemci süresi alması önemliyse, kabul edin - bekleyebilirim'. İşte bunun nasıl çalıştığına dair basit bir örnek:

public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
Sırayla üç iş parçacığı oluşturup başlatıyoruz: Thread-0, Thread-1, ve Thread-2. Thread-0önce başlar ve hemen diğerlerine yol verir. Sonra Thread-1başlar ve ayrıca verir. Sonra Thread-2başlar, bu da verir. Başka ileti dizimiz yok ve Thread-2en sondaki yerini verdikten sonra, ileti dizisi planlayıcı, 'Hmm, başka yeni ileti dizisi yok' diyor. Sırada kimler var? Daha önce yerini kim verdi Thread-2? Görünüşe göre öyleydi Thread-1. Tamam, bu, çalışmasına izin vereceğimiz anlamına geliyor'. Thread-1işini tamamlar ve ardından iş parçacığı planlayıcı koordinasyonuna devam eder: 'Tamam, Thread-1bitti. Kuyrukta bizden başka kimse var mı?'. Thread-0 sırada: hemen önce yerini verdiThread-1. Şimdi sırasını alıyor ve tamamlanmak üzere koşuyor. Ardından programlayıcı, dizileri koordine etmeyi bitirir: "Tamam, Thread-2, diğer dizilere teslim oldunuz ve şimdi hepsi bitti. Son teslim olan sendin, şimdi sıra sende'. Ardından Thread-2tamamlanmaya çalışır. Konsol çıktısı şu şekilde görünecektir: Thread-0 yerini başkalarına verir Thread-1 yerini başkalarına verir Thread-2 yerini başkalarına verir Thread-1 yürütmeyi bitirdi. Thread-0 yürütmeyi bitirdi. Thread-2'nin yürütülmesi tamamlandı. Elbette, iş parçacığı planlayıcı, iş parçacıklarını farklı bir sırayla başlatabilir (örneğin, 0-1-2 yerine 2-1-0), ancak prensip aynı kalır.

Olur-öncesi kurallar

Bugün değineceğimiz son şey ' önceden olur ' kavramıdır. Bildiğiniz gibi, Java'da iş parçacığı zamanlayıcısı, görevlerini yerine getirmek için iş parçacıklarına zaman ve kaynak ayırma işinin büyük bölümünü gerçekleştirir. Ayrıca iş parçacıklarının genellikle tahmin edilmesi imkansız olan rastgele bir sırayla nasıl yürütüldüğünü defalarca gördünüz. Ve genel olarak, daha önce yaptığımız 'sıralı' programlamadan sonra, çok iş parçacıklı programlama rastgele bir şeye benziyor. Çok iş parçacıklı bir programın akışını kontrol etmek için bir dizi yöntem kullanabileceğinize zaten inanmaya başladınız. Ancak Java'da çoklu iş parçacığı kullanımının bir ayağı daha vardır - 4 " önceden olur " kuralı. Bu kuralları anlamak oldukça basittir. İki iş parçacığımız olduğunu hayal edin - AveB. Bu iş parçacıklarının her biri işlemleri gerçekleştirebilir 1ve 2. Her kuralda, ' A, B'den önce olur ' dediğimizde , iş parçacığı tarafından Aişlemden önce yapılan tüm değişikliklerin ve bu işlemden kaynaklanan değişikliklerin, işlem yapıldığında ve sonrasında iş parçacığı 1tarafından görülebildiğini kastediyoruz . Her kural, çok iş parçacıklı bir program yazdığınızda, belirli olayların diğerlerinden %100 önce gerçekleşeceğini ve iş parçacığının işlem sırasında iş parçacığının işlem sırasında yaptığı değişikliklerden her zaman haberdar olacağını garanti eder . Onları gözden geçirelim. B22BA1

Kural 1.

Bir muteksin serbest bırakılması, aynı monitör başka bir iş parçacığı tarafından alınmadan önce gerçekleşir . Bence burada her şeyi anlıyorsun. Bir nesnenin veya sınıfın muteksi bir iş parçacığı tarafından, örneğin, iş parçacığı tarafından alınırsa A, başka bir iş parçacığı (thread B) aynı anda onu alamaz. Mutex serbest bırakılana kadar beklemesi gerekir.

Kural 2.

Yöntem Thread.start()daha önce gerçekleşir Thread.run() . Yine, burada zor bir şey yok. Yöntemin içindeki kodu çalıştırmaya başlamak için yöntemi iş parçacığında run()çağırmanız gerektiğini zaten biliyorsunuz. start()Spesifik olarak, start yöntemi, run()yöntemin kendisi değil! Bu kural, çağrılmadan önce ayarlanan tüm değişkenlerin değerlerinin, bir kez başladığında yöntem Thread.start()içinde görünür olmasını sağlar.run()

Kural 3.

run()Yöntemin sonu, yöntemden dönüşten önce gerçekleşirjoin() . İki konu başlığımıza dönelim: Ave B. Yöntemi çağırıyoruz join(), böylece iş parçacığı işini yapmadan önce Biş parçacığının tamamlanmasını beklemesi garanti edilir . ABu, A nesnesinin run()yönteminin sonuna kadar çalışmasının garanti edildiği anlamına gelir. run()Ve iş parçacığı yönteminde meydana gelen verilerdeki tüm değişikliklerin, iş parçacığının kendi işine başlayabilmesi için işini bitirmesini bekleyerek bittiğinde Aiş parçacığında görünür olması yüzde yüz garantilidir .BA

Kural 4.

volatileBir değişkene yazmak, aynı değişkenden okumadan önce gerçekleşir . Anahtar kelimeyi kullandığımızda volatileaslında her zaman o anki değeri elde ederiz. Bir longveya ile bile double(burada olabilecek sorunlardan daha önce bahsetmiştik). Zaten anladığınız gibi, bazı başlıklarda yapılan değişiklikler her zaman diğer başlıklar tarafından görülmez. Ancak, elbette, bu tür davranışların bize uymadığı çok sık durumlar vardır. Dizideki bir değişkene bir değer atadığımızı varsayalım A:

int z;

….

z = 555;
İş parçacığımız Bdeğişkenin değerini zkonsolda gösterecekse, atanan değeri bilmediği için kolaylıkla 0 görüntüleyebilir. zAncak Kural 4, değişkeni olarak bildirirsek volatile, bir iş parçacığında değerinde yapılan değişikliklerin başka bir iş parçacığında her zaman görünür olacağını garanti eder. volatileÖnceki koddaki kelimeye eklersek ...

volatile int z;

….

z = 555;
B...o zaman iş parçacığının 0 gösterebileceği durumu engelleriz. volatileDeğişkenlere yazmak, onlardan okumadan önce gerçekleşir.
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION