ฉันจะได้รับสถานการณ์อย่างง่าย ๆ เช่นการสอนที่แนะนำว่าควรใช้สิ่งนี้อย่างไรกับคิวโดยเฉพาะ?
ฉันจะได้รับสถานการณ์อย่างง่าย ๆ เช่นการสอนที่แนะนำว่าควรใช้สิ่งนี้อย่างไรกับคิวโดยเฉพาะ?
คำตอบ:
wait()และnotify()วิธีการได้รับการออกแบบเพื่อให้กลไกที่จะอนุญาตให้ด้ายบล็อกจนกว่าเงื่อนไขที่เฉพาะเจาะจงจะพบ สำหรับเรื่องนี้ฉันคิดว่าคุณต้องการที่จะเขียนการใช้งานคิวการบล็อคซึ่งคุณมีร้านค้าสำรองขนาดคงที่ขององค์ประกอบ
สิ่งแรกที่คุณต้องทำคือการระบุเงื่อนไขที่คุณต้องการวิธีการที่จะรอ ในกรณีนี้คุณจะต้องการput()วิธีการบล็อกจนกว่าจะมีพื้นที่ว่างในร้านค้าและคุณจะต้องการtake()วิธีการบล็อกจนกว่าจะมีองค์ประกอบที่จะกลับมา
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;
    }
}มีบางสิ่งที่ควรทราบเกี่ยวกับวิธีการที่คุณต้องใช้กลไกการรอและแจ้งเตือน
ประการแรกคุณต้องตรวจสอบให้แน่ใจว่าการโทรไปยังwait()หรือnotify()อยู่ในขอบเขตการซิงโครไนซ์ของรหัส (ด้วยการโทรwait()และnotify()การซิงโครไนซ์บนวัตถุเดียวกัน) เหตุผลสำหรับสิ่งนี้ (นอกเหนือจากความกังวลเรื่องความปลอดภัยของเธรดมาตรฐาน) เกิดจากสิ่งที่เรียกว่าสัญญาณที่ไม่ได้รับ
ตัวอย่างของสิ่งนี้คือว่าเธรดอาจเรียกใช้put()เมื่อคิวเกิดขึ้นเต็มจากนั้นตรวจสอบเงื่อนไขดูว่าคิวเต็มอย่างไรก็ตามก่อนที่จะสามารถบล็อกเธรดอื่นถูกกำหนดเวลาไว้ เธรดที่สองนี้take()จะเป็นองค์ประกอบจากคิวและแจ้งให้เธรดที่รอทราบว่าคิวไม่เต็มอีกต่อไป เนื่องจากเธรดแรกได้ตรวจสอบเงื่อนไขแล้วอย่างไรก็ตามมันจะทำการเรียกwait()หลังจากที่ถูกจัดตารางใหม่อีกครั้งแม้ว่าจะสามารถดำเนินการได้
โดยการซิงโครไนซ์บนวัตถุที่ใช้ร่วมกันคุณสามารถมั่นใจได้ว่าปัญหานี้จะไม่เกิดขึ้นเนื่องจากการtake()เรียกเธรดที่สองจะไม่สามารถดำเนินการได้จนกว่าเธรดแรกจะถูกบล็อกจริง
ประการที่สองคุณต้องใส่เงื่อนไขที่คุณกำลังตรวจสอบในขณะที่ห่วงแทนที่จะเป็นคำสั่งถ้าเนื่องจากปัญหาที่รู้จักกันเป็นเสแสร้งปลุก นี่คือตำแหน่งที่เธรดการรอสามารถเปิดใช้งานอีกครั้งโดยไม่notify()ถูกเรียก การวางการตรวจสอบนี้ในขณะที่ลูปจะช่วยให้มั่นใจได้ว่าหากมีการปลุกปลอมเกิดขึ้นเงื่อนไขจะถูกตรวจสอบอีกครั้งและเธรดจะเรียกwait()อีกครั้ง
ดังที่คำตอบอื่น ๆ ได้กล่าวถึง Java 1.5 ได้แนะนำไลบรารีการทำงานพร้อมกันใหม่ (ในjava.util.concurrentแพ็คเกจ) ซึ่งได้รับการออกแบบมาเพื่อให้มีความเป็นนามธรรมในระดับที่สูงกว่ากลไกการรอ / แจ้งเตือน ด้วยการใช้คุณสมบัติใหม่เหล่านี้คุณสามารถเขียนตัวอย่างต้นฉบับใหม่ได้เช่น:
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();
        }
    }
}แน่นอนถ้าคุณต้องการคิวการบล็อกจริง ๆ แล้วคุณควรใช้ ส่วนต่อประสานBlockingQueue
นอกจากนี้สำหรับสิ่งเช่นนี้ฉันขอแนะนำJava Concurrency ในทางปฏิบัติเนื่องจากครอบคลุมทุกสิ่งที่คุณต้องการทราบเกี่ยวกับปัญหาและวิธีแก้ไขที่เกี่ยวข้องกับการเกิดพร้อมกัน
ไม่ใช่ตัวอย่างของคิว แต่ง่ายมาก :)
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();
        }
    }
}ประเด็นสำคัญบางประการ: 
1) ไม่เคยทำ
 if(!pizzaArrived){
     wait();
 }ใช้ในขณะที่ (เงื่อนไข) เสมอเพราะ
while(!pizzaExists){ wait(); }(ตัวอย่างเช่นอาจจะดีกว่าที่จะเป็น2) คุณต้องระงับการล็อค (ซิงโครไนซ์) ก่อนที่จะเรียกใช้ wait / nofity หัวข้อต้องได้รับล็อคก่อนที่จะตื่น
3) พยายามหลีกเลี่ยงการได้รับการล็อคใด ๆ ภายในบล็อกที่ซิงโครไนซ์ของคุณและพยายามอย่าเรียกใช้วิธีการต่างดาว (วิธีที่คุณไม่รู้ว่ามันกำลังทำอะไรอยู่) หากคุณต้องตรวจสอบให้แน่ใจว่าได้ใช้มาตรการเพื่อหลีกเลี่ยงการหยุดชะงัก
4) ระมัดระวังด้วยการแจ้งเตือน () ติดกับ INFORMAll () จนกว่าคุณจะรู้ว่าคุณกำลังทำอะไรอยู่
5) สุดท้าย แต่ไม่ท้ายสุดอ่านJava Concurrency ในทางปฏิบัติ !
pizzaArrivedธง หากมีการเปลี่ยนแปลงการตั้งค่าสถานะโดยไม่มีการเรียกnotifyมันจะไม่มีผลใด ๆ นอกจากนี้ยังมีเพียงwaitและnotifyเรียกตัวอย่างการทำงาน
                    synchronizedคีย์เวิร์ดจะมีการซ้ำซ้อนเพื่อประกาศตัวแปรvolatileและแนะนำให้หลีกเลี่ยงความสับสน @mrida
                    แม้ว่าคุณจะขอwait()และnotify()เจาะจง แต่ฉันรู้สึกว่าคำพูดนี้ยังมีความสำคัญพอ:
Josh Bloch, Java รุ่นที่ 2 ที่มีประสิทธิภาพ , รายการ 69: แนะนำยูทิลิตี้การทำงานพร้อมกันให้waitและnotify(เน้นที่เขา):
ได้รับความยากลำบากของการใช้
waitและnotifyถูกต้องคุณควรใช้ระดับสูงสาธารณูปโภคเห็นพ้องแทน [ ... ] โดยใช้waitและnotifyโดยตรงเป็นเหมือนการเขียนโปรแกรมใน "ภาษาประกอบการเห็นพ้อง"java.util.concurrentเมื่อเทียบกับภาษาระดับสูงให้บริการโดย ไม่ค่อยมีหากเคยมีเหตุผลที่จะใช้waitและnotifyในรหัสใหม่
notify()และwait()อีกต่อไป
                    คุณดูที่บทช่วยสอนนี้แล้วหรือยัง
นอกจากนี้ฉันขอแนะนำให้คุณหลีกเลี่ยงการเล่นกับสิ่งต่าง ๆ ในซอฟต์แวร์จริง มันดีที่จะเล่นกับมันเพื่อที่คุณจะได้รู้ว่ามันคืออะไร แต่การเกิดขึ้นพร้อมกันนั้นมีข้อผิดพลาดทั่วทุกที่ เป็นการดีกว่าที่จะใช้ abstractions ระดับสูงขึ้นและคอลเลกชันที่ถูกซิงโครไนซ์หรือคิว JMS หากคุณกำลังสร้างซอฟต์แวร์สำหรับคนอื่น
นั่นคือสิ่งที่ฉันทำ ฉันไม่ใช่ผู้เชี่ยวชาญในการทำงานพร้อมกันดังนั้นฉันจึงไม่ต้องจัดการกับกระทู้ด้วยมือทุกครั้งที่ทำได้
ตัวอย่าง
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
     }
}ตัวอย่างการรอ () และ alertall () ในเธรด
รายการอาเรย์แบบคงที่ที่ซิงโครไนซ์จะใช้เป็นทรัพยากรและเมธอด wait () จะถูกเรียกใช้ถ้ารายการอาเรย์นั้นว่างเปล่า วิธีการแจ้งเตือน () ถูกเรียกเมื่อองค์ประกอบถูกเพิ่มสำหรับรายการอาร์เรย์
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)อีกฉันคิดว่ามันอาจจะผิดพลาด
                    
notifyปลุกเพียงหนึ่งเธรด หากสองเธรดผู้บริโภคแข่งขันเพื่อลบองค์ประกอบหนึ่งการแจ้งเตือนอาจปลุกเธรดผู้บริโภคอื่นซึ่งไม่สามารถทำอะไรเกี่ยวกับมันและจะกลับไปนอน (แทนที่จะเป็นผู้ผลิตซึ่งเราหวังว่าจะแทรกองค์ประกอบใหม่) เพราะ เธรดโปรดิวเซอร์ไม่ได้ถูกปลุกไม่มีสิ่งใดแทรกและตอนนี้ทั้งสามเธรดจะนอนไม่ จำกัด ฉันลบความคิดเห็นก่อนหน้าของฉันตามที่กล่าวไว้ (ผิด) ว่าการปลุกลวงตาเป็นสาเหตุของปัญหา (ไม่ใช่)