giriiş
İplikler ilginç bir şey. Geçmiş incelemelerde, çoklu iş parçacığını uygulamak için mevcut araçlardan bazılarına baktık. Başka ne ilginç şeyler yapabileceğimize bir bakalım. Bu noktada çok şey biliyoruz. Örneğin, "
Birlikte daha iyi: Java ve Thread sınıfı. Bölüm I — Yürütme evreleri "nden, Thread sınıfının bir yürütme dizisini temsil ettiğini biliyoruz. Bir iş parçacığının bazı görevleri yerine getirdiğini biliyoruz. Eğer görevlerimizin yapabilmesini istiyorsak
run
, konuyu ile işaretlememiz gerekir
Runnable
.
Hatırlamak için Tutorialspoint Online Java Compiler'ı![Birlikte daha iyi: Java ve Thread sınıfı. Bölüm VI - Ateş edin! - 1]()
kullanabiliriz :
public static void main(String[] args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
Ayrıca kilit denen bir şeye sahip olduğumuzu da biliyoruz.
Bunu " Birlikte daha iyi: Java ve Thread sınıfı. Kısım II — Senkronizasyon'da öğrendik . Bir iş parçacığı bir kilit alırsa, kilidi almaya çalışan başka bir iş parçacığı kilidin serbest bırakılmasını beklemeye zorlanır:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
Sanırım yapabileceğimiz başka ilginç şeyler hakkında konuşmanın zamanı geldi.
Semaforlar
Aynı anda kaç iş parçacığının çalışabileceğini kontrol etmenin en basit yolu bir semafordur. Demiryolu sinyali gibi. Yeşil, devam anlamına gelir. Kırmızı, bekle demektir. Semafordan ne bekleyelim? Erişim. Erişmek için onu almalıyız. Erişime artık ihtiyaç kalmadığında, onu vermeli veya serbest bırakmalıyız. Bunun nasıl çalıştığını görelim. Sınıfı içe aktarmamız gerekiyor
java.util.concurrent.Semaphore
. Örnek:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
Gördüğünüz gibi, bu işlemler (alma ve bırakma) bir semaforun nasıl çalıştığını anlamamıza yardımcı olur. En önemli şey, eğer erişim elde edeceksek, semaforun pozitif sayıda izne sahip olması gerektiğidir. Bu sayı, negatif bir sayı olarak başlatılabilir. Ve 1'den fazla izin talebinde bulunabilir (alabiliriz).
Geri Sayım Mandalı
Bir sonraki mekanizma
CountDownLatch
. Şaşırtıcı olmayan bir şekilde, bu geri sayımlı bir mandal. Burada sınıf için uygun import ifadesine ihtiyacımız var
java.util.concurrent.CountDownLatch
. Herkesin başlangıç çizgisinde toplandığı bir ayak yarışı gibi. Ve herkes hazır olduğunda, herkes aynı anda başlama sinyalini alır ve aynı anda başlar. Örnek:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
İlk önce mandala söyleyelim
countDown()
. Google, geri sayımı "sıfırdan geriye doğru sayma eylemi" olarak tanımlar. Ve sonra mandala şunu söyleriz
await()
, yani sayaç sıfır olana kadar bekleyin. İlginç bir şekilde, bu tek seferlik bir sayaçtır. Java belgeleri, "İpliklerin bu şekilde art arda geri sayması gerektiğinde, bunun yerine bir CyclicBarrier kullanın" diyor. Başka bir deyişle, yeniden kullanılabilir bir sayaca ihtiyacınız varsa, farklı bir seçeneğe ihtiyacınız vardır:
CyclicBarrier
.
Döngüsel Bariyer
Adından da anlaşılacağı gibi,
CyclicBarrier
"yeniden kullanılabilir" bir bariyerdir. Sınıfı içe aktarmamız gerekecek
java.util.concurrent.CyclicBarrier
. Bir örneğe bakalım:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Gördüğünüz gibi thread
await
metodu çalıştırıyor yani bekliyor. Bu durumda bariyer değeri düşer.
barrier.isBroken()
Geri sayım sıfıra ulaştığında bariyer kırılmış ( ) kabul edilir . Bariyeri sıfırlamak için, sahip olmayan
reset()
yöntemi çağırmanız gerekir.
CountDownLatch
eşanjör
Bir sonraki mekanizma Eşanjördür. Bu bağlamda, bir Değişim, bir şeylerin değiş tokuş edildiği veya değiştirildiği bir senkronizasyon noktasıdır. Beklediğiniz gibi an,
Exchanger
takas veya takas gerçekleştiren bir sınıftır. En basit örneğe bakalım:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
Burada iki iş parçacığı başlatıyoruz. Her biri değişim yöntemini çalıştırır ve diğer iş parçacığının da değişim yöntemini çalıştırmasını bekler. Bunu yaparken, ileti dizileri iletilen bağımsız değişkenleri değiş tokuş eder. İlginç. Size bir şey hatırlatmıyor mu?
SynchronousQueue
Kalbinde yatan 'ı anımsatıyor
CachedThreadPool
. Netlik için, işte bir örnek:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
Örnek, yeni bir iş parçacığı başlatıldığında sıra boş olacağından bekleyeceğini göstermektedir. Ve sonra ana iş parçacığı "Mesaj" dizesini kuyruğa koyar. Ayrıca, bu dizi sıradan alınana kadar duracaktır.
Bu konu hakkında daha fazla bilgi edinmek için " SynchronousQueue ve Exchanger " makalesini de okuyabilirsiniz .
fazer
En iyisini sona sakladık —
Phaser
. Sınıfı içe aktarmamız gerekecek
java.util.concurrent.Phaser
. Basit bir örneğe bakalım:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
// By calling the register method, we register the current (main) thread as a party
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
// After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
// We indicate that there will be a +1 party on the Phaser
phaser.register();
// Start a new thread
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
System.out.println(name + " after passing barrier");
}).start();
}
Örnek, kullanılırken
Phaser
, kayıt sayısı engele gelenlerin sayısıyla eşleştiğinde engelin kırıldığını göstermektedir.
Bu GeeksforGeeks makalesiniPhaser
okuyarak daha fazla aşina olabilirsiniz .
Özet
Bu örneklerden de görebileceğiniz gibi, dizileri senkronize etmenin çeşitli yolları vardır. Daha önce, çoklu okumanın özelliklerini hatırlamaya çalıştım. Umarım bu serideki önceki taksitler faydalı olmuştur. Bazı insanlar çoklu iş parçacığına giden yolun "Uygulamada Java Eş Zamanlılığı" kitabıyla başladığını söylüyor. 2006 yılında piyasaya sürülmesine rağmen, insanlar kitabın oldukça temel olduğunu ve bugün hala geçerli olduğunu söylüyor. Örneğin, tartışmayı buradan okuyabilirsiniz:
"Uygulamada Java Eş Zamanlılığı" hala geçerli mi? . Tartışmadaki bağlantıları okumak da yararlıdır.
Örneğin, The Well-Grounded Java Developer kitabına bir bağlantı var ve Bölüm 4'ten özellikle bahsedeceğiz.
Modern eşzamanlılık . Bu konuyla ilgili tam bir inceleme de var:
"Uygulamada Java Eş Zamanlılığı" Java 8 Çağında Hala Geçerli mi? Bu makale ayrıca, bu konuyu gerçekten anlamak için başka ne okumanız gerektiğine dair öneriler sunar.
Ardından OCA/OCP Java SE 8 Programmer Practice Tests gibi harika bir kitaba göz atabilirsiniz . İkinci kısaltmayla ilgileniyoruz: OCP (Oracle Certified Professional). Testleri "Bölüm 20: Java Eş Zamanlılığı"nda bulacaksınız. Bu kitapta hem sorular hem de açıklamalarla birlikte cevaplar var. Örneğin:
![Birlikte daha iyi: Java ve Thread sınıfı. Bölüm VI - Ateş edin! - 3]()
Pek çok kişi bu sorunun yöntemlerin ezberlenmesine başka bir örnek olduğunu söylemeye başlayabilir. Bir yandan, evet.
ExecutorService
Öte yandan, bu soruyu bunun bir tür "yükseltme" olduğunu hatırlayarak yanıtlayabilirsiniz
Executor
. Ve
Executor
Runnable
sadece iş parçacıklarının oluşturulma şeklini gizlemeyi amaçlar, ancak bunları yürütmenin, yani yeni bir iş parçacığında bir nesne başlatmanın ana yolu değildir . Bu yüzden yoktur
execute(Callable)
- çünkü içinde
ExecutorService
,
Executor
basitçe
submit()
bir nesneyi döndürebilecek yöntemler ekler
Future
. Tabii ki, bir yöntem listesi ezberleyebiliriz, ancak sınıfların doğası hakkındaki bilgimize dayanarak yanıtımızı vermek çok daha kolaydır. Ve işte konuyla ilgili bazı ek materyaller:
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 II — Eşitleme 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
GO TO FULL VERSION