จะใช้การรอและการแจ้งเตือนใน Java ได้อย่างไรโดยไม่ใช้


129

ฉันมีเมทริกซ์ 2 ตัวและต้องคูณพวกมันแล้วพิมพ์ผลลัพธ์ของแต่ละเซลล์ ทันทีที่เซลล์หนึ่งพร้อมฉันต้องพิมพ์ แต่ตัวอย่างเช่นฉันต้องพิมพ์เซลล์ [0] [0] ก่อนเซลล์ [2] [0] แม้ว่าผลลัพธ์ของ [2] [0] จะพร้อมก่อน . ดังนั้นฉันต้องพิมพ์ตามคำสั่ง ดังนั้นความคิดของฉันคือการทำให้เธรดเครื่องพิมพ์รอจนกว่าmultiplyThreadจะแจ้งให้ทราบว่าเซลล์ที่ถูกต้องพร้อมที่จะพิมพ์แล้วprinterThreadจะพิมพ์เซลล์และกลับไปรอเป็นต้น

ดังนั้นฉันมีหัวข้อนี้ที่จะคูณ:

public void run() 
{
    int countNumOfActions = 0; // How many multiplications have we done
    int maxActions = randomize(); // Maximum number of actions allowed

    for (int i = 0; i < size; i++)
    {       
        result[rowNum][colNum] = result[rowNum][colNum] + row[i] * col[i];
        countNumOfActions++;
        // Reached the number of allowed actions
        if (countNumOfActions >= maxActions)
        {
            countNumOfActions = 0;
            maxActions = randomize();
            yield();
        }   
    }
    isFinished[rowNum][colNum] = true;
    notify();
}

เธรดที่พิมพ์ผลลัพธ์ของแต่ละเซลล์:

public void run()
{
    int j = 0; // Columns counter
    int i = 0; // Rows counter
    System.out.println("The result matrix of the multiplication is:");

    while (i < creator.getmThreads().length)
    {
        synchronized (this)
        {
            try 
            {
                this.wait();
            } 
            catch (InterruptedException e1) 
            {
            }
        }
        if (creator.getmThreads()[i][j].getIsFinished()[i][j] == true)
        {
            if (j < creator.getmThreads()[i].length)
            {
                System.out.print(creator.getResult()[i][j] + " ");
                j++;
            }
            else
            {
                System.out.println();
                j = 0;
                i++;
                System.out.print(creator.getResult()[i][j] + " ");
            }
        }
    }

ตอนนี้มันทำให้ฉันมีข้อยกเว้นเหล่านี้:

Exception in thread "Thread-9" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-6" Exception in thread "Thread-4" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-5" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-8" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-7" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-11" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-10" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-12" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)

บรรทัด 49 ใน multiplyThreadคือ "แจ้งเตือน ()" .. ฉันคิดว่าฉันต้องใช้การซิงโครไนซ์ต่างกัน แต่ฉันไม่แน่ใจว่าจะทำอย่างไร

หากใครสามารถช่วยให้รหัสนี้ทำงานฉันจะขอบคุณมันจริงๆ

คำตอบ:


215

เพื่อให้สามารถโทรแจ้ง ()คุณต้องซิงโครไนซ์กับวัตถุเดียวกัน

synchronized (someObject) {
    someObject.wait();
}

/* different thread / object */
synchronized (someObject) {
    someObject.notify();
}

29
while(!JobCompleted);ตัวเลือกโดยทั่วไปเป็นความคิดที่ดีเพราะมันผูก CPU ของคุณที่ 100% ตรวจสอบตัวแปรเดียวกันอย่างต่อเนื่อง (ดูที่นี่ )
แมตต์ลียง

5
while(!JobCompleted) Thread.sleep(5); ไม่มีปัญหานั้น
BeniBela

15
มันยังคงมีปัญหาในการเป็นสิ่งที่แตกต่างอย่างสิ้นเชิง การสำรวจ (การตรวจสอบซ้ำ ๆ ว่าเงื่อนไขบางอย่างตรงกับสิ่งที่คุณกำลังทำ) โดยทั่วไปแล้วจะไม่ค่อยได้รับการแจ้งเตือนหากมีการเปลี่ยนแปลงเงื่อนไขดังกล่าว (เช่นสิ่งที่ฉันระบุไว้ในคำตอบ)
Bombe

3
@huseyintugrulbuyukisik คุณสามารถโทรหาwaitเมื่อใดก็ตามที่เธรดปัจจุบันมีการล็อกบนวัตถุที่waitเรียกว่า ไม่ว่าคุณจะใช้synchronizedบล็อกหรือวิธีการซิงโครไนซ์นั้นขึ้นอยู่กับคุณแล้ว
Bombe

1
@BeniBela แต่คาดว่าจะช้าลงอย่างแน่นอน (เกี่ยวกับคำถามดั้งเดิมของ Huseyin)
โทมัส

64

ในขณะที่ใช้งานwaitและnotifyหรือnotifyAllวิธีการใน Java ต้องจำสิ่งต่อไปนี้:

  1. ใช้notifyAllแทนnotifyหากคุณคาดว่าจะมีเธรดมากกว่าหนึ่งเธรดกำลังรอการล็อก
  2. waitและnotifyวิธีการจะต้องเรียกว่าในบริบทที่ตรงกันวิธีการจะต้องเรียกว่าในบริบทที่ตรงกันดูลิงค์สำหรับคำอธิบายรายละเอียดเพิ่มเติม
  3. เรียกwait()วิธีการนี้เสมอในการวนซ้ำเพราะถ้าหลายเธรดกำลังรอการล็อกและหนึ่งในนั้นได้รับการล็อกและรีเซ็ตเงื่อนไขดังนั้นเธรดอื่น ๆ จำเป็นต้องตรวจสอบเงื่อนไขหลังจากที่พวกเขาตื่นขึ้นมาเพื่อดูว่าพวกเขาต้องรออีกครั้งหรือไม่ สามารถเริ่มการประมวลผล
  4. ใช้วัตถุเดียวกันสำหรับการโทรwait()และnotify()วิธีการ วัตถุทุกชิ้นมีล็อคของตัวเองดังนั้นการเรียกwait()ใช้วัตถุ A และnotify()บนวัตถุ B จะไม่สมเหตุสมผล

21

คุณต้องการที่จะด้ายนี้หรือไม่? ฉันสงสัยว่าเมทริกซ์ของคุณใหญ่แค่ไหนและมีประโยชน์ไหมในการพิมพ์เธรดหนึ่งอันในขณะที่อีกเธรดทำการคูณ

บางทีมันอาจจะคุ้มค่าที่จะวัดในครั้งนี้ก่อนที่จะทำเกลียวที่ค่อนข้างซับซ้อน?

หากคุณต้องการใช้เธรดฉันจะสร้างเธรด 'n' เพื่อดำเนินการคูณเซลล์ (อาจเป็น 'n' คือจำนวนแกนประมวลผลที่มีให้คุณ) จากนั้นใช้กลไกExecutorServiceและFutureเพื่อส่งหลายการคูณพร้อมกัน .

ด้วยวิธีนี้คุณสามารถปรับการทำงานให้เหมาะสมตามจำนวนแกนประมวลผลและคุณกำลังใช้เครื่องมือเธรด Java ระดับสูงขึ้น (ซึ่งจะทำให้ชีวิตง่ายขึ้น) เขียนผลลัพธ์กลับสู่เมทริกซ์การรับแล้วพิมพ์เพียงครั้งเดียวเมื่องานในอนาคตของคุณเสร็จสมบูรณ์


1
+1 @Greg ฉันคิดว่าคุณควรดูแพ็คเกจ java.util.concurrent ตามที่ Brian อธิบาย
ATorras

1
+1 และลองอ่านหนังสือเล่มนี้ซึ่งจะสอนวิธีที่ถูกต้องในการใช้การรอ () และแจ้ง () jcip.net
Chii

14

สมมติว่าคุณมี 'กล่องดำ' การประยุกต์ใช้กับการเรียนบางชื่อที่มีวิธีการBlackBoxClassdoSomething();

นอกจากนี้คุณยังมีผู้สังเกตการณ์หรือผู้ฟังonResponse(String resp)ที่จะถูกเรียกBlackBoxClassหลังจากเวลาที่ไม่รู้จัก

การไหลง่าย

private String mResponse = null; 
 ...
BlackBoxClass bbc = new BlackBoxClass();
   bbc.doSomething();
...
@override
public void onResponse(String resp){        
      mResponse = resp;       
}

ให้บอกว่าเราไม่ทราบว่าเกิดอะไรขึ้นกับBlackBoxClassและเมื่อเราควรได้รับคำตอบ แต่คุณไม่ต้องการที่จะดำเนินการต่อรหัสของคุณจนกว่าคุณจะได้รับคำตอบหรือในคำอื่น ๆ ที่ได้รับการonResponseโทร ที่นี่เข้าสู่ 'ประสานช่วยเหลือ':

public class SyncronizeObj {
public void doWait(long l){
    synchronized(this){
        try {
            this.wait(l);
        } catch(InterruptedException e) {
        }
    }
}

public void doNotify() {
    synchronized(this) {
        this.notify();
    }
}

public void doWait() {
    synchronized(this){
        try {
            this.wait();
        } catch(InterruptedException e) {
        }
    }
}
}

ตอนนี้เราสามารถใช้สิ่งที่เราต้องการ:

public class Demo {

private String mResponse = null; 
 ...
SyncronizeObj sync = new SyncronizeObj();

public void impl(){

BlackBoxClass bbc = new BlackBoxClass();
   bbc.doSomething();

   if(mResponse == null){
      sync.doWait();
    }

/** at this momoent you sure that you got response from  BlackBoxClass because
  onResponse method released your 'wait'. In other cases if you don't want wait too      
  long (for example wait data from socket) you can use doWait(time) 
*/ 
...

}


@override
public void onResponse(String resp){        
      mResponse = resp;
      sync.doNotify();       
   }

}

7

คุณสามารถโทรแจ้งบนวัตถุที่คุณเป็นเจ้าของจอภาพเท่านั้น ดังนั้นคุณต้องการสิ่งที่ต้องการ

synchronized(threadObject)
{
   threadObject.notify();
}


3

ฉันจะยกตัวอย่างง่ายๆให้คุณเห็นวิธีการใช้waitและnotifyจาวาที่ถูกต้อง ดังนั้นฉันจะสร้างสองชั้นชื่อThreadA & ThreadB ThreadA จะเรียกใช้ ThreadB

public class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();//<----Create Instance for seconde class
        b.start();//<--------------------Launch thread

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();//<-------------WAIT until the finish thread for class B finish
            }catch(InterruptedException e){
                e.printStackTrace();
            }

            System.out.println("Total is: " + b.total);
        }
    }
} 

และสำหรับ ThreadB ระดับ:

class ThreadB extends Thread{
    int total;
    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            notify();//<----------------Notify the class wich wait until my    finish 
//and tell that I'm finish
            }
        }
    }

3

ใช้ง่ายถ้าคุณต้องการวิธีการรันเธรดหรือ: -

public class MyThread {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(() -> {
            try {
                synchronized (lock) {
                    for (int i = 0; i <= 5; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + "A");
                        lock.notify();
                        lock.wait();
                    }
                }
            } catch (Exception e) {}
        }, "T1").start();

        new Thread(() -> {
            try {
                synchronized (lock) {
                    for (int i = 0; i <= 5; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + "B");
                        lock.notify();
                        lock.wait();
                    }
                }
            } catch (Exception e) {}
        }, "T2").start();
    }
}

การตอบสนอง: -

T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B

มันทำงานอย่างไรเมื่อฉันมีการดำเนินการ 4 อย่างที่จะดำเนินการในแบบซิงโครนัส?
saksham agarwal

2

เราสามารถโทรแจ้งเพื่อกลับมาทำงานการรอวัตถุเป็น

public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

ดำเนินการนี้ต่อโดยเรียกใช้การแจ้งเตือนบนวัตถุอื่นของคลาสเดียวกัน

public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}

0

สำหรับปัญหาเฉพาะนี้ทำไมไม่เก็บผลลัพธ์ต่าง ๆ ของคุณในตัวแปรแล้วเมื่อประมวลผลเธรดสุดท้ายของคุณคุณสามารถพิมพ์ในรูปแบบใดก็ได้ที่คุณต้องการ สิ่งนี้มีประโยชน์อย่างยิ่งหากคุณกำลังจะใช้ประวัติการทำงานของคุณในโครงการอื่น


0

ดูเหมือนว่าสถานการณ์สำหรับรูปแบบผู้ผลิตและผู้บริโภค หากคุณกำลังใช้จาวา 5 หรือสูงกว่าคุณอาจลองใช้การบล็อคคิว (java.util.concurrent.BlockingQueue) และปล่อยให้การประสานงานของเธรดทำงานกับการใช้งานเฟรมเวิร์ก / api ดูตัวอย่างจาก java 5: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html หรือ java 7 (ตัวอย่างเดียวกัน): http: // docs oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html


0

คุณได้ป้องกันบล็อครหัสอย่างถูกต้องเมื่อคุณเรียกwait()ใช้วิธีการโดยใช้synchronized(this)วิธีการโดยใช้

แต่คุณไม่ได้ใช้ความระมัดระวังเหมือนกันเมื่อคุณเรียกnotify()ใช้วิธีการโดยไม่ใช้บล็อกที่มีการป้องกัน:synchronized(this)หรือsynchronized(someObject)

ถ้าคุณดูที่หน้าเอกสารของ Oracle บนวัตถุชั้นซึ่งมีwait(), notify(), notifyAll()วิธีการที่คุณสามารถดูด้านล่างระมัดระวังในเรื่องเหล่านี้ทั้งหมดสามวิธี

วิธีการนี้ควรถูกเรียกโดยเธรดที่เป็นเจ้าของจอภาพของวัตถุนี้เท่านั้น

หลายสิ่งมีการเปลี่ยนแปลงใน 7 ปีที่ผ่านมาและลองมาดูทางเลือกอื่น ๆsynchronizedในคำถาม SE ด้านล่าง:

เหตุใดจึงต้องใช้ ReentrantLock ถ้าใครสามารถใช้ข้อมูลให้ตรงกัน (นี่)

การประสานเทียบกับล็อค

หลีกเลี่ยงการซิงโครไนซ์ (นี่) ใน Java หรือไม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.