ฉันจะได้รับสถานการณ์อย่างง่าย ๆ เช่นการสอนที่แนะนำว่าควรใช้สิ่งนี้อย่างไรกับคิวโดยเฉพาะ?
ฉันจะได้รับสถานการณ์อย่างง่าย ๆ เช่นการสอนที่แนะนำว่าควรใช้สิ่งนี้อย่างไรกับคิวโดยเฉพาะ?
คำตอบ:
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
ปลุกเพียงหนึ่งเธรด หากสองเธรดผู้บริโภคแข่งขันเพื่อลบองค์ประกอบหนึ่งการแจ้งเตือนอาจปลุกเธรดผู้บริโภคอื่นซึ่งไม่สามารถทำอะไรเกี่ยวกับมันและจะกลับไปนอน (แทนที่จะเป็นผู้ผลิตซึ่งเราหวังว่าจะแทรกองค์ประกอบใหม่) เพราะ เธรดโปรดิวเซอร์ไม่ได้ถูกปลุกไม่มีสิ่งใดแทรกและตอนนี้ทั้งสามเธรดจะนอนไม่ จำกัด ฉันลบความคิดเห็นก่อนหน้าของฉันตามที่กล่าวไว้ (ผิด) ว่าการปลุกลวงตาเป็นสาเหตุของปัญหา (ไม่ใช่)