CodeGym /Java Blogu /Rastgele /Birlikte daha iyi: Java ve Thread sınıfı. Bölüm II - Senk...
John Squirrels
Seviye
San Francisco

Birlikte daha iyi: Java ve Thread sınıfı. Bölüm II - Senkronizasyon

grupta yayınlandı

giriiş

Yani, Java'nın iş parçacıklarına sahip olduğunu biliyoruz. Bunun hakkında Daha İyi Birlikte: Java ve İplik sınıfı başlıklı incelemede okuyabilirsiniz . Bölüm I - Yürütme konuları . Paralel olarak iş yapmak için dişler gereklidir. Bu, iş parçacıklarının bir şekilde birbirleriyle etkileşime girmesini büyük olasılıkla sağlar. Bunun nasıl olduğuna ve hangi temel araçlara sahip olduğumuza bakalım. Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II - Senkronizasyon - 1

Teslim olmak

Thread.yield() şaşırtıcıdır ve nadiren kullanılır. İnternette birçok farklı şekilde anlatılıyor. Bir iş parçacığının iş parçacığı önceliklerine göre ineceği bazı iş parçacığı kuyruğu olduğunu yazan bazı insanlar dahil. Diğer insanlar, bir iş parçacığının durumunu "Çalışıyor"dan "Çalıştırılabilir"e değiştireceğini yazıyor (bu durumlar arasında bir ayrım olmamasına rağmen, yani Java bunlar arasında ayrım yapmıyor). Gerçek şu ki, hepsi çok daha az biliniyor ve yine de bir anlamda daha basit. Yöntemin belgeleri için günlüğe kaydedilen bir hata ( JDK-6416721: (spec thread) Fix Thread.yield() javadocBirlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 2 ) var . Okursanız, açıkça görülüyor ki,yield()yield()yöntem aslında Java iş parçacığı planlayıcısına yalnızca bu iş parçacığına daha az yürütme süresi verilebileceği konusunda bazı önerilerde bulunur. Ancak gerçekte ne olduğu, yani programlayıcının tavsiyeye göre hareket edip etmediği ve genel olarak ne yaptığı, JVM'nin uygulamasına ve işletim sistemine bağlıdır. Ve diğer bazı faktörlere de bağlı olabilir. Tüm kafa karışıklığı, büyük olasılıkla, Java dili geliştikçe çoklu okumanın yeniden düşünülmesinden kaynaklanmaktadır. Buradaki genel bakışta daha fazlasını okuyun: Java Thread.yield()'a Kısa Giriş .

Uyumak

Bir iş parçacığı yürütülürken uyku moduna geçebilir. Bu, diğer ileti dizileriyle en kolay etkileşim türüdür. Java kodumuzu çalıştıran Java sanal makinesini çalıştıran işletim sisteminin kendi iş parçacığı zamanlayıcısı vardır . Hangi iş parçacığının ne zaman başlayacağına karar verir. Bir programcı bu programlayıcıyla doğrudan Java kodundan etkileşim kuramaz, yalnızca JVM aracılığıyla. Programlayıcıdan diziyi bir süre duraklatmasını, yani uyku moduna almasını isteyebilir. Şu makalelerde daha fazlasını okuyabilirsiniz: Thread.sleep() ve How Multithreading work . Windows işletim sistemlerinde thread'lerin nasıl çalıştığını da inceleyebilirsiniz: Internals of Windows Thread . Ve şimdi kendi gözlerimizle görelim. Aşağıdaki kodu adlı bir dosyaya kaydedin HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Gördüğünüz gibi, 60 saniye bekleyen bir görevimiz var ve ardından program sona eriyor. " " komutunu kullanarak derliyoruz javac HelloWorldApp.javave ardından " " kullanarak programı çalıştırıyoruz java HelloWorldApp. Programı ayrı bir pencerede başlatmak en iyisidir. Örneğin, Windows'ta şu şekildedir: start java HelloWorldApp. PID (process ID) almak için jps komutunu kullanıyoruz ve " ile thread listesini açıyoruz jvisualvm --openpid pid: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II - Senkronizasyon - 3Gördüğünüz gibi threadimiz artık "Sleeping" statüsüne sahip. Aslında daha şık bir yardım yolu var konumuzun tatlı rüyalar görmesi:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Her yerle ilgilendiğimizi fark ettiniz mi InterruptedException? Nedenini anlayalım.

Konu.interrupt()

Mesele şu ki, bir iş parçacığı beklerken/uykudayken birisi araya girmek isteyebilir. Bu durumda, bir InterruptedException. Bu mekanizma, yöntemin Kullanımdan Kaldırıldığı, yani modası geçmiş ve istenmeyen olarak ilan edilmesinden sonra oluşturulmuştur Thread.stop(). Bunun nedeni, stop()yöntem çağrıldığında, iş parçacığının basitçe "öldürülmesi" idi ki bu çok tahmin edilemezdi. İş parçacığının ne zaman durdurulacağını bilemedik ve veri tutarlılığını garanti edemedik. İş parçacığı öldürülürken bir dosyaya veri yazdığınızı hayal edin. Diziyi öldürmek yerine, Java'nın yaratıcıları ona kesilmesi gerektiğini söylemenin daha mantıklı olacağına karar verdiler. Bu bilgilere nasıl yanıt verileceği, ileti dizisinin kendisinin karar vereceği bir konudur. Daha fazla ayrıntı için Thread.stop neden kullanımdan kaldırıldı? bölümünü okuyun.Oracle'ın web sitesinde. Bir örneğe bakalım:

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Bu örnekte 60 saniye beklemeyeceğiz. Bunun yerine, hemen "Kesildi" görüntüleyeceğiz. interrupt()Bunun nedeni , iş parçacığındaki yöntemi çağırmamızdır . Bu yöntem, "kesme durumu" adı verilen dahili bir işaret ayarlar. Yani, her iş parçacığının doğrudan erişilemeyen bir dahili bayrağı vardır. Ancak bu bayrakla etkileşime geçmek için yerel yöntemlerimiz var. Ama tek yol bu değil. Bir iş parçacığı çalışıyor olabilir, bir şey beklemiyor olabilir, sadece eylemler gerçekleştiriyor olabilir. Ancak başkalarının işini belirli bir zamanda bitirmek isteyeceğini tahmin edebilir. Örneğin:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Yukarıdaki örnekte, whileiş parçacığı harici olarak kesilene kadar döngü yürütülecektir. Bayrağa gelince isInterrupted, şunu bilmek önemlidir, eğer bir yakalarsak InterruptedExceptionisInterrupted bayrağı sıfırlanır ve ardından isInterrupted()false döndürür. Thread sınıfı ayrıca yalnızca geçerli iş parçacığı için geçerli olan statik bir Thread.interrupted() yöntemine sahiptir, ancak bu yöntem bayrağı yanlış olarak sıfırlar! Konu Kesintisi başlıklı bu bölümde daha fazlasını okuyun .

Katıl (Başka bir konunun bitmesini bekleyin)

En basit bekleme türü, başka bir iş parçacığının bitmesini beklemektir.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Bu örnekte, yeni iş parçacığı 5 saniye uyuyacaktır. Aynı zamanda, ana iş parçacığı uykudaki iş parçacığı uyanana ve işini bitirene kadar bekleyecektir. JVisualVM'de iş parçacığının durumuna bakarsanız, şöyle görünecektir: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 4İzleme araçları sayesinde, iş parçacığında neler olduğunu görebilirsiniz. Yöntem oldukça basittir, çünkü yalnızca çağrıldığı iş parçacığı canlı olduğu sürece joinyürütülen Java koduna sahip bir yöntemdir . wait()İplik ölür ölmez (işini bitirdiğinde) bekleme kesilir. Ve bu, yöntemin tüm büyüsüdür join(). Öyleyse en ilginç şeye geçelim.

monitör

Çoklu iş parçacığı, monitör kavramını içerir. Monitör kelimesi İngilizce'ye 16. yüzyıl Latince'sinden gelir ve "bir sürecin sürekli kaydını tutmak, kontrol etmek veya gözlemlemek için kullanılan bir alet veya cihaz" anlamına gelir. Bu makale bağlamında, temelleri ele almaya çalışacağız. Ayrıntıları isteyenler için, lütfen bağlantılı materyallere dalın. Yolculuğumuza Java Dil Belirtimi (JLS) ile başlıyoruz: 17.1. senkronizasyon . Aşağıdakileri söylüyor: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 5Görünüşe göre Java, iş parçacıkları arasında senkronizasyon için bir "monitör" mekanizması kullanıyor. Her nesneyle bir monitör ilişkilendirilir ve iş parçacıkları onu ile alabilir lock()veya ile serbest bırakabilir unlock(). Ardından, öğreticiyi Oracle web sitesinde bulacağız: İçsel Kilitler ve Senkronizasyon. Bu öğretici, Java'nın eşitlemesinin gerçek kilit veya monitör kilidi adı verilen dahili bir varlık etrafında oluşturulduğunu söylüyor . Bu kilide genellikle basitçe " monitör " denir . Ayrıca, Java'daki her nesnenin kendisiyle ilişkilendirilmiş bir içsel kilide sahip olduğunu tekrar görüyoruz. Java - İçsel Kilitler ve Senkronizasyon'u okuyabilirsiniz . Daha sonra, Java'daki bir nesnenin bir monitörle nasıl ilişkilendirilebileceğini anlamak önemli olacaktır. Java'da her nesnenin, programcının koddan elde edemediği, ancak sanal makinenin nesnelerle doğru şekilde çalışması için ihtiyaç duyduğu dahili meta verileri depolayan bir başlığı vardır. Nesne başlığı, şuna benzeyen bir "işaret sözcüğü" içerir: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

İşte çok faydalı bir JavaWorld makalesi: Java sanal makinesi iş parçacığı eşitlemesini nasıl gerçekleştirir ? Bu makale, JDK hata izleme sistemindeki şu sorunun "Özet" bölümündeki açıklamayla birleştirilmelidir: JDK-8183909 . Aynı şeyi burada da okuyabilirsiniz: JEP-8183909 . Bu nedenle, Java'da bir monitör bir nesneyle ilişkilendirilir ve iş parçacığı kilidi almaya (veya almaya) çalıştığında bir iş parçacığını engellemek için kullanılır. İşte en basit örnek:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Burada, geçerli iş parçacığı (bu kod satırlarının üzerinde yürütüldüğü iş parçacığı), anahtar synchronizedkelimeyi kullanarak ilişkili monitörü kullanmaya çalışır.object"\kilidi almak/elde etmek için değişken. Başka hiç kimse monitör için yarışmıyorsa (başka bir deyişle, aynı nesneyi kullanan başka hiç kimse senkronize kod çalıştırmıyorsa), o zaman Java "yanlı kilitleme" adı verilen bir optimizasyon gerçekleştirmeye çalışabilir. İlgili bir etiket ve monitörün kilidinin hangi iş parçacığına ait olduğuna dair bir kayıt, nesne başlığındaki işaret sözcüğüne eklenir. Bu, bir monitörü kilitlemek için gereken yükü azaltır. Monitör daha önce başka bir iş parçacığına aitse, bu tür bir kilitleme yeterli değildir. JVM bir sonraki kilitleme türüne geçer: "temel kilitleme". Karşılaştır ve değiştir (CAS) işlemlerini kullanır. Dahası, nesne başlığının işaret kelimesinin kendisi artık işaret kelimesini saklamaz, bunun yerine depolandığı yere bir referans verir ve JVM'nin temel kilitleme kullandığımızı anlaması için etiket değişir. Birden fazla iş parçacığı bir monitör için rekabet ederse (yarışırsa) (biri kilidi aldı ve bir saniye kilidin serbest bırakılmasını bekliyor), o zaman işaret sözcüğündeki etiket değişir ve işaret sözcüğü artık monitöre bir referans depolar bir nesne olarak - JVM'nin bazı dahili varlıkları. JDK Enchancement Proposal'da (JEP) belirtildiği gibi, bu durum bu varlığı depolamak için belleğin Native Heap alanında yer gerektirir. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. ve bir saniye kilidin serbest bırakılmasını bekler), ardından işaret sözcüğündeki etiket değişir ve işaret sözcüğü artık monitöre bir referansı JVM'nin bazı dahili varlıkları olarak bir nesne olarak depolar. JDK Enchancement Proposal'da (JEP) belirtildiği gibi, bu durum bu varlığı depolamak için belleğin Native Heap alanında yer gerektirir. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. ve bir saniye kilidin serbest bırakılmasını bekler), ardından işaret sözcüğündeki etiket değişir ve işaret sözcüğü artık monitöre bir referansı JVM'nin bazı dahili varlıkları olarak bir nesne olarak depolar. JDK Enchancement Proposal'da (JEP) belirtildiği gibi, bu durum bu varlığı depolamak için belleğin Native Heap alanında yer gerektirir. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. ve işaret sözcüğü artık monitöre bir referansı bir nesne - JVM'nin bazı dahili varlıkları - olarak saklar. JDK Enchancement Proposal'da (JEP) belirtildiği gibi, bu durum bu varlığı depolamak için belleğin Native Heap alanında yer gerektirir. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. ve işaret sözcüğü artık monitöre bir referansı bir nesne - JVM'nin bazı dahili varlıkları - olarak saklar. JDK Enchancement Proposal'da (JEP) belirtildiği gibi, bu durum bu varlığı depolamak için belleğin Native Heap alanında yer gerektirir. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. Bu dahili varlığın hafıza konumuna yapılan referans, nesne başlığının işaret kelimesinde saklanacaktır. Bu nedenle, bir monitör gerçekten birden fazla iş parçacığı arasında paylaşılan kaynaklara erişimi senkronize etmek için bir mekanizmadır. JVM, bu mekanizmanın birkaç uygulaması arasında geçiş yapar. Basit olması açısından, monitörden bahsederken aslında kilitlerden bahsediyoruz. Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 7

Senkronize (kilit bekliyor)

Daha önce gördüğümüz gibi, "eşzamanlı blok" (veya "kritik bölüm") kavramı, monitör kavramıyla yakından ilişkilidir. Bir örneğe göz atın:

public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Burada ana iş parçacığı önce görev nesnesini yeni iş parçacığına geçirir ve ardından hemen kilidi alır ve onunla uzun bir işlem gerçekleştirir (8 saniye). Bunca zaman, görev devam edemiyor çünkü bloğa giremiyor synchronizedçünkü kilit zaten alınmış. İplik kilidi alamazsa, monitörü bekler. Kilidi alır almaz yürütmeye devam edecektir. Bir iş parçacığı monitörden çıktığında kilidi serbest bırakır. JVisualVM'de şuna benzer: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 8JVisualVM'de görebileceğiniz gibi durum "Monitor", yani thread bloke olmuş ve monitörü alamıyor. Bir iş parçacığının durumunu belirlemek için de kod kullanabilirsiniz, ancak bu şekilde belirlenen durum adları, benzer olsalar da JVisualVM'de kullanılan adlarla eşleşmez. bu durumda,th1.getState()for döngüsündeki ifade BLOCKED döndürür , çünkü döngü çalıştığı sürece locknesnenin monitörü iş parçacığı tarafından işgal edilir mainve th1iş parçacığı bloke edilir ve kilit serbest bırakılana kadar ilerleyemez. Senkronize bloklara ek olarak, bir yöntemin tamamı senkronize edilebilir. Örneğin, sınıftan bir yöntem HashTable:

public synchronized int size() {
	return count;
}
Bu yöntem, herhangi bir zamanda yalnızca bir iş parçacığı tarafından yürütülecektir. Gerçekten kilide ihtiyacımız var mı? Evet, ihtiyacımız var. Örnek yöntemler söz konusu olduğunda, "bu" nesne (mevcut nesne) bir kilit görevi görür. Burada bu konuyla ilgili ilginç bir tartışma var: Senkronize Blok yerine Senkronize Yöntem kullanmanın bir avantajı var mı? . Yöntem statik ise, kilit "bu" nesne olmaz (çünkü statik bir yöntem için "bu" nesne yoktur), bunun yerine bir Class nesnesi (örneğin, Integer.class) olacaktır.

Bekle (bir monitör bekleniyor). notify() ve notifyAll() yöntemleri

Thread sınıfı, bir monitörle ilişkilendirilmiş başka bir bekleme yöntemine sahiptir. sleep()ve ' den farklı olarak join(), bu yöntem basitçe çağrılamaz. Adı wait(). Yöntem wait, beklemek istediğimiz monitörle ilişkili nesnede çağrılır. Bir örnek görelim:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
JVisualVM'de şöyle görünür: Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 10Bunun nasıl çalıştığını anlamak için, wait()ve notify()yöntemlerinin java.lang.Object. İş parçacığıyla ilgili yöntemlerin sınıfta olması garip gelebilir Object. Ancak bunun nedeni şimdi ortaya çıkıyor. Java'daki her nesnenin bir başlığı olduğunu hatırlayacaksınız. Başlık, monitör hakkında bilgiler, yani kilidin durumu dahil olmak üzere çeşitli temizlik bilgilerini içerir. Her nesnenin veya bir sınıfın örneğinin, JVM'de içsel kilit veya monitör adı verilen dahili bir varlıkla ilişkili olduğunu unutmayın. Yukarıdaki örnekte, görev nesnesinin kodu, nesneyle ilişkili monitör için senkronize bloğa girdiğimizi gösterir lock. Bu monitör için kilidi almayı başarırsak, o zamanwait()denir. Görevi yürüten iş parçacığı, nesnenin monitörünü serbest bırakacak , ancak nesnenin monitöründen lockbildirim bekleyen iş parçacığı kuyruğuna girecektir . lockBu iş parçacığı kuyruğuna, amacını daha doğru bir şekilde yansıtan BEKLEME SETİ adı verilir. Yani, bir sıradan çok bir kümedir. İş parçacığı main, görev nesnesiyle yeni bir iş parçacığı oluşturur, onu başlatır ve 3 saniye bekler. Bu, yeni iş parçacığının, iş parçacığından önce kilidi alabilmesi mainve monitörün kuyruğuna girmesi olasılığının yüksek olmasını sağlar. Bundan sonra, mainiş parçacığının kendisi locknesnenin senkronize bloğuna girer ve monitörü kullanarak iş parçacığı bildirimi gerçekleştirir. Bildirim gönderildikten sonra, mainiş parçacığılocklocknesnenin izleyicisi ve daha önce nesnenin izleyicisinin serbest bırakılmasını bekleyen yeni iş parçacığı yürütmeye devam eder. notify()Yalnızca bir konuya ( ) veya sıradaki tüm iş parçacıklarına ( ) aynı anda bildirim göndermek mümkündür notifyAll(). Burada daha fazlasını okuyun: Java'da notify() ve notifyAll() arasındaki fark . Bildirim sırasının JVM'nin nasıl uygulandığına bağlı olduğuna dikkat etmek önemlidir. Burada daha fazlasını okuyun: Notify ve notifyAll ile açlığı nasıl çözebilirim? . Senkronizasyon, bir nesne belirtilmeden gerçekleştirilebilir. Bunu, tek bir kod bloğu yerine tüm bir yöntem senkronize edildiğinde yapabilirsiniz. Örneğin, statik yöntemler için kilit bir Class nesnesi olacaktır ( aracılığıyla elde edilir .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Kilit kullanımı açısından her iki yöntem de aynıdır. instanceBir yöntem statik değilse, senkronizasyon geçerli kullanılarak , yani kullanılarak gerçekleştirilir this. getState()Bu arada, yöntemi bir iş parçacığının durumunu almak için kullanabileceğinizi daha önce söylemiştik . Örneğin, kuyruktaki bir izleyici bekleyen iş parçacığı için, yöntem wait()bir zaman aşımı belirtmişse, durum BEKLENİYOR veya TIMED_WAITING olacaktır. Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

İplik yaşam döngüsü

Bir iş parçacığının ömrü boyunca durumu değişir. Aslında, bu değişiklikler iş parçacığının yaşam döngüsünü oluşturur. Bir iş parçacığı oluşturulur oluşturulmaz durumu YENİ olur. Bu durumda, yeni iş parçacığı henüz çalışmıyor ve Java iş parçacığı planlayıcısı henüz onun hakkında hiçbir şey bilmiyor. İş parçacığı zamanlayıcısının iş parçacığı hakkında bilgi edinmesi için yöntemi çağırmanız gerekir thread.start(). Ardından iş parçacığı RUNNABLE durumuna geçecektir. İnternette, "Çalıştırılabilir" ve "Çalışıyor" durumları arasında ayrım yapan birçok yanlış diyagram vardır. Ancak bu bir hatadır, çünkü Java "çalışmaya hazır" (çalıştırılabilir) ve "çalışıyor" (çalışıyor) arasında ayrım yapmaz. Bir iş parçacığı canlı olduğunda ancak etkin olmadığında (Çalıştırılabilir değil), iki durumdan birindedir:
  • BLOCKED — kritik bir bölüme, yani bir bloğa girmeyi bekliyor synchronized.
  • BEKLEMEK — bir koşulu yerine getirmek için başka bir iş parçacığının beklenmesi.
Koşul karşılanırsa, iş parçacığı planlayıcı iş parçacığını başlatır. İş parçacığı belirli bir süreye kadar bekliyorsa durumu TIMED_WAITING olur. İş parçacığı artık çalışmıyorsa (bitti veya bir istisna atıldı), ardından SONLANDIRILDI durumuna girer. Bir iş parçacığının durumunu öğrenmek için getState()yöntemi kullanın. İş parçacıklarının ayrıca isAlive(), iş parçacığı TERMINATED değilse doğru döndüren bir yöntemi vardır.

LockSupport ve iş parçacığı parkı

Java 1.6 ile başlayarak, LockSupport adlı ilginç bir mekanizma ortaya çıktı. Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 12Bu sınıf, onu kullanan her iş parçacığıyla bir "izin" ilişkilendirir. Yönteme yapılan bir çağrı, park()izin varsa, süreçteki izni tüketerek hemen geri döner. Aksi halde engeller. Yöntemin çağrılması, unparkhenüz mevcut değilse iznin kullanılabilir olmasını sağlar. Sadece 1 izin var. için Java belgeleri, LockSupportsınıfa atıfta bulunur Semaphore. Basit bir örneğe bakalım:

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Bu kod her zaman bekleyecek çünkü artık semaforun 0 izni var. Ve acquire()kodda çağrıldığında (örn. izin talep edin), iş parçacığı izni alana kadar bekler. Madem bekliyoruz, halletmeliyiz InterruptedException. İlginç bir şekilde, semafor ayrı bir iş parçacığı durumu alır. JVisualVM'de bakarsak, durumun "Bekle" değil, "Park" olduğunu görürüz. Birlikte daha iyi: Java ve Thread sınıfı.  Bölüm II — Senkronizasyon - 13Başka bir örneğe bakalım:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
İş parçacığının durumu BEKLENİYOR olacaktır, ancak JVisualVM, anahtar waitkelimeden synchronizedve parksınıftan ayırır LockSupport. Bu neden LockSupportbu kadar önemli? Tekrar Java belgelerine dönüyoruz ve BEKLENİYOR iş parçacığı durumuna bakıyoruz. Gördüğünüz gibi, içine girmenin sadece üç yolu var. Bu yollardan ikisi wait()ve join(). Ve üçüncüsü LockSupport. Java'da kilitler ayrıca LockSupport üzerine inşa edilebilir ve daha üst düzey araçlar sunar. Birini kullanmayı deneyelim. Örneğin, şuna bir göz atın ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Tıpkı önceki örneklerde olduğu gibi, burada her şey basit. Nesne lock, birinin paylaşılan kaynağı serbest bırakmasını bekler. mainJVisualVM'ye bakarsak, yeni iş parçacığının , iş parçacığı kilidini açana kadar park edileceğini görürüz . Kilitler hakkında daha fazla bilgiyi buradan edinebilirsiniz: Java 8 StampedLocks ve ReadWriteLocks ve Java'da Eşitlenmiş ve Kilit API'si. Kilitlerin nasıl uygulandığını daha iyi anlamak için Phaser ile ilgili şu makaleyi okumak faydalı olacaktır: Java Phaser Rehberi . Çeşitli eşleyicilerden bahsetmişken, Java Eşitleyicileri ile ilgili DZone makalesini okumalısınız .

Çözüm

Bu derlemede, iş parçacıklarının Java'da etkileşime girdiği ana yolları inceledik. Ek malzeme: Birlikte daha iyi: Java ve Thread sınıfı. Bölüm I — Yürütme konuları Birlikte daha iyi: Java ve Thread sınıfı. Bölüm III — Etkileşim Birlikte Daha İyi: Java ve Thread sınıfı. Bölüm IV — Callable, Future ve arkadaşlar Birlikte daha iyi: Java ve Thread sınıfı. Bölüm V — Yürütücü, ThreadPool, Fork/Join Birlikte daha iyi: Java ve Thread sınıfı. Bölüm VI - Ateş edin!
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION