Yanıtlar:
wait()
Ve notify()
yöntem, belirli bir koşul karşılanmış oluncaya dek bloğa bir iplik izin vermek için bir mekanizma sağlamak üzere tasarlanmıştır. Bunun için bazı sabit boyutlu destek deponuzun bulunduğu bir engelleme kuyruğu uygulaması yazmak istediğinizi varsayalım.
Yapmanız gereken ilk şey, yöntemlerin beklemesini istediğiniz koşulları tanımlamaktır. Bu durumda, put()
depoda boş alan take()
olana kadar yöntemin engellenmesini ve döndürülecek bir öğe olana kadar yöntemin engellenmesini istersiniz .
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size() == capacity) {
wait();
}
queue.add(element);
notify(); // notifyAll() for multiple producer/consumer threads
}
public synchronized T take() throws InterruptedException {
while(queue.isEmpty()) {
wait();
}
T item = queue.remove();
notify(); // notifyAll() for multiple producer/consumer threads
return item;
}
}
Bekleme ve bildirim mekanizmalarını nasıl kullanmanız gerektiği hakkında dikkat edilmesi gereken birkaç nokta vardır.
Öncelikle, kodun senkronize edilmiş bir bölgesine ( veya çağrılarının aynı nesne üzerinde senkronize edilmesiyle) eşitlenmiş wait()
veya notify()
içinde olduğundan emin olmanız gerekir . Bunun nedeni (standart iplik güvenliği endişeleri dışında), kaçırılan sinyal olarak bilinen bir şeyden kaynaklanmaktadır.wait()
notify()
Bunun bir örneği put()
, kuyruk bir dolu olduğunda bir iş parçacığının arayabileceği , daha sonra koşulu kontrol edebileceği, kuyruğun dolu olduğunu, ancak başka bir iş parçacığını engelleyebilmesinden önce programlanabileceğidir. Bu ikinci iş parçacığı take()
, kuyruktaki bir öğedir ve bekleyen iş parçacıklarının artık dolu olmadığını bildirir. Bununla birlikte, ilk iş parçacığı durumu zaten kontrol ettiğinden, wait()
ilerleme kaydedebilse bile yeniden programlandıktan sonra arayacaktır .
İkinci bir iş parçacığının take()
çağrısı, ilk iş parçacığı gerçekten engellenene kadar ilerleme sağlayamayacağından , paylaşılan bir nesne üzerinde eşitleme yaparak bu sorunun oluşmamasını sağlayabilirsiniz.
İkinci olarak, sahte uyandırma olarak bilinen bir sorun nedeniyle, if ifadesi yerine bir while döngüsüne kontrol ettiğiniz koşulu koymanız gerekir. Burada bekleyen bir iş parçacığı bazen notify()
çağrılmadan yeniden etkinleştirilebilir . Bu kontrolü bir while döngüsüne koymak, sahte bir uyandırma gerçekleşirse durumun tekrar kontrol edilmesini ve iş parçacığının tekrar aranmasını sağlar wait()
.
Diğer bazı cevapların da belirttiği gibi, Java 1.5 java.util.concurrent
, bekleme / bildirim mekanizması üzerinde daha yüksek bir soyutlama sağlamak için tasarlanmış yeni bir eşzamanlılık kütüphanesi ( pakette) tanıttı . Bu yeni özellikleri kullanarak orijinal örneği şu şekilde yeniden yazabilirsiniz:
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while(queue.size() == capacity) {
notFull.await();
}
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
T item = queue.remove();
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
Tabii ki aslında bir engelleme sırasına ihtiyacınız varsa, o zaman BlockingQueue arabiriminin bir uygulamasını kullanmalısınız .
Ayrıca, bunun gibi şeyler için , uygulamada Java Concurrency'u tavsiye ederim , çünkü eşzamanlılık ile ilgili sorunlar ve çözümleri hakkında bilmek isteyebileceğiniz her şeyi kapsar.
Bir kuyruk örneği değil, ama son derece basit :)
class MyHouse {
private boolean pizzaArrived = false;
public void eatPizza(){
synchronized(this){
while(!pizzaArrived){
wait();
}
}
System.out.println("yumyum..");
}
public void pizzaGuy(){
synchronized(this){
this.pizzaArrived = true;
notifyAll();
}
}
}
Bazı önemli noktalar:
1) ASLA yapmayın
if(!pizzaArrived){
wait();
}
Her zaman while (koşul) kullanın, çünkü
while(!pizzaExists){ wait(); }
.2) Bekleme / nofity çağırmadan önce kilidi (senkronize) tutmanız gerekir. İplikler ayrıca uyanmadan önce kilit almak zorundadır.
3) Senkronize bloğunuzda herhangi bir kilit almaktan kaçının ve yabancı yöntemleri (ne yaptığını bilmediğiniz yöntemler bilmediğiniz yöntemler) çağırmamaya çalışın. Gerekirse, kilitlenmeleri önlemek için önlem aldığınızdan emin olun.
4) Bildirime () dikkat edin. Ne yaptığınızı bilinceye kadar notifyAll () ile devam edin.
5) Son olarak, fakat aynı derecede önemli olarak , Uygulamada Java Eşzamanlılığı'nı okuyun !
pizzaArrived
bayrağı kullanıyorsunuz ? bayrak çağrılmadan değiştirilirse notify
, herhangi bir etkisi olmaz. Ayrıca sadece birlikte wait
ve notify
örnek eserler çağırır.
synchronized
anahtar kelime ile korunduğunda , değişkeni bildirmek gereksizdir volatile
ve karışıklığı önlemek için bundan kaçınılması önerilir mrida
Siz istediniz wait()
ve notify()
özellikle, bu teklifin hala yeterince önemli olduğunu hissediyorum:
Josh Bloch, Etkili Java 2.Baskı, Öğe 69: Eşzamanlılık yardımcı programlarını tercih edin wait
ve notify
(vurgulayın):
Kullanmanın zorluğu
wait
venotify
doğru göz önüne alındığında, bunun yerine daha üst düzey eşzamanlılık yardımcı programlarını kullanmalısınız [...]wait
venotify
doğrudan sağlanan "eşzamanlılık dilini" programlama ile benzerdirjava.util.concurrent
. Nadiren de olsa kullanım koduwait
venotify
yeni kod vardır .
notify()
ve wait()
tekrar
Bu Java Eğiticisine bir göz attınız mı?
Ayrıca, gerçek yazılımda bu tür şeylerle oynamaktan uzak durmanızı tavsiye ederim. Onunla oynamak güzel, bu yüzden ne olduğunu biliyorsun, ama eşzamanlılığın her yerinde tuzaklar var. Başkaları için yazılım oluşturuyorsanız daha üst düzey soyutlamalar ve senkronize edilmiş koleksiyonlar veya JMS kuyrukları kullanmak daha iyidir.
En azından benim yaptığım bu. Bir eşzamanlılık uzmanı değilim, bu yüzden mümkün olan her yerde iş parçacıklarını elle işlemekten uzak duruyorum.
Misal
public class myThread extends Thread{
@override
public void run(){
while(true){
threadCondWait();// Circle waiting...
//bla bla bla bla
}
}
public synchronized void threadCondWait(){
while(myCondition){
wait();//Comminucate with notify()
}
}
}
public class myAnotherThread extends Thread{
@override
public void run(){
//Bla Bla bla
notify();//Trigger wait() Next Step
}
}
İş Parçacığındaki wait () ve notifyall () örneği.
Senkronize edilmiş statik dizi listesi kaynak olarak kullanılır ve dizi listesi boşsa wait () yöntemi çağrılır. dizi listesi için bir öğe eklendiğinde notify () yöntemi çağrılır.
public class PrinterResource extends Thread{
//resource
public static List<String> arrayList = new ArrayList<String>();
public void addElement(String a){
//System.out.println("Add element method "+this.getName());
synchronized (arrayList) {
arrayList.add(a);
arrayList.notifyAll();
}
}
public void removeElement(){
//System.out.println("Remove element method "+this.getName());
synchronized (arrayList) {
if(arrayList.size() == 0){
try {
arrayList.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
arrayList.remove(0);
}
}
}
public void run(){
System.out.println("Thread name -- "+this.getName());
if(!this.getName().equalsIgnoreCase("p4")){
this.removeElement();
}
this.addElement("threads");
}
public static void main(String[] args) {
PrinterResource p1 = new PrinterResource();
p1.setName("p1");
p1.start();
PrinterResource p2 = new PrinterResource();
p2.setName("p2");
p2.start();
PrinterResource p3 = new PrinterResource();
p3.setName("p3");
p3.start();
PrinterResource p4 = new PrinterResource();
p4.setName("p4");
p4.start();
try{
p1.join();
p2.join();
p3.join();
p4.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final size of arraylist "+arrayList.size());
}
}
if(arrayList.size() == 0)
, ben burada bir hata olabilir düşünüyorum.
notify
sadece bir iş parçacığını uyandırır. İki tüketici iş parçacığı bir öğeyi kaldırmak için yarışıyorsa, bir bildirim diğer tüketici iş parçacığını uyandırabilir, bu da onun hakkında hiçbir şey yapamaz ve uykuya geri döner (üreticinin yerine yeni bir öğe eklemesini umuyoruz). Çünkü üretici iş parçacığı uyandırılmaz, hiçbir şey sokulmaz ve şimdi üç iş parçacığı da süresiz olarak uyur. Sahte uyandırmanın sorunun nedeni olduğunu söylediğim gibi (yanlış değil) önceki