Java同步:synchronized、wait()、notify()

11

我正在尝试理解Java中的线程间通信,并了解到可以使用wait()notify()notifyAll()方法来实现。

为了让线程执行这些方法之一,线程需要成为调用(任何一个)方法的对象的lock的所有者。此外,所有这些方法都需要在一个synchronized块/方法中。到目前为止,一切正常。

我尝试编写了一个程序,在其中一个线程打印奇数,另一个线程打印偶数。程序可以正确运行,但同时也引发了一些疑问。

下面是我实现的程序的完整源代码。

PrintEvenNumThread.java // 打印偶数

package com.example.multithr.implrun;

import com.example.common.ObjectToWaitOn;

public class PrintEvenNumThread implements Runnable {

    private ObjectToWaitOn objectToWaitOn;

    public PrintEvenNumThread(ObjectToWaitOn objectToWaitOn) {
        this.objectToWaitOn = objectToWaitOn;
    }

    @Override
    public void run() {

    int numToPrint = 2;

    for (;;) {
        synchronized (objectToWaitOn) {
            while(objectToWaitOn.getPrintEvenOrOdd() != 2) {
                try {
                    objectToWaitOn.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }   
            }
                objectToWaitOn.print("EvenThread",numToPrint);
                numToPrint += 2; // Generate next even number
                objectToWaitOn.setPrintEvenOrOdd(1);
                objectToWaitOn.notifyAll();
            }
        }
    }
}

PrintOddNumsThread.java // 打印奇数

package com.example.multithr.implrun;

import com.example.common.ObjectToWaitOn;

public class PrintOddNumsThread implements Runnable {

    private ObjectToWaitOn objectToWaitOn;

    public PrintOddNumsThread(ObjectToWaitOn objectToWaitOn) {
        this.objectToWaitOn = objectToWaitOn;
    }

    @Override
    public void run() {
        int numToPrint = 1;

        for(;;) {

            synchronized(objectToWaitOn) {

                while(objectToWaitOn.getPrintEvenOrOdd() != 1) {  
                    try {
                        objectToWaitOn.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                objectToWaitOn.print("OddThread", numToPrint);
                numToPrint += 2; // Generate next odd number
                objectToWaitOn.setPrintEvenOrOdd(2);
                objectToWaitOn.notifyAll();
            }
        }
    }
}

ObjectToWaitOn.java // 用于线程间通信的“共享”对象

package com.vipin.common;

public class ObjectToWaitOn {

    private int printEvenOrOdd;

    public ObjectToWaitOn(int printEvenOrOdd) {
        this.printEvenOrOdd = printEvenOrOdd;
    }

    public int getPrintEvenOrOdd() {
        return printEvenOrOdd;
    }

    public void setPrintEvenOrOdd(int printEvenOrOdd) {
        this.printEvenOrOdd = printEvenOrOdd;
    }

    public void print(String byThread, int numToPrint) {
        System.out.println(byThread + ": " +numToPrint);
    }
}

PrintEvenOddNumsMainApp.java

package com.example.multithr.main.app1;

import com.example.common.ObjectToWaitOn;
import com.example.multithr.implrun.PrintEvenNumThread;
import com.example.multithr.implrun.PrintOddNumsThread;

    public class PrintEvenOddNumsMainApp {

        public static void main(String[] args) {

            ObjectToWaitOn obj = new ObjectToWaitOn(1); // 1 == odd; 2 == even

            PrintEvenNumThread printEvenNumThread = new PrintEvenNumThread(obj);
            PrintOddNumsThread printOddNumsThread = new PrintOddNumsThread(obj);

            Thread evenNum = new Thread(printEvenNumThread);
            Thread oddNum = new Thread(printOddNumsThread);

            evenNum.start();
            oddNum.start();
        }
    }

我的疑问是:

1)当这些线程中的任何一个通过调用notifyAll()在共享的objectToWaitOn对象上释放锁定时,它是否立即释放锁定?我有这个疑问,因为这些线程基于objectToWaitOn对象的synchronized块;因此,即使线程调用了notifyAll(),它是否仍然保持锁定,因为它处于同步块中

2) 当线程通过在objectToWaitOn上调用wait()处于等待状态时,如果其他线程通过调用notifyAll()释放了锁定,则等待线程会等待锁定释放还是其他事情?不管怎样,一个线程离开同步块时总会释放它所持有的对象上的锁定;因此,在上面的示例中,如果一个线程持有objectToWaitOn的锁定并离开同步块,它是否会为objectToWaitOn释放锁定,并且根据这个,其他线程会醒来吗?

谁能帮我澄清这些疑虑?

1个回答

7

它会立即释放锁吗?

不会。线程会在同步块内继续执行下一条语句。

但是,它不应该因为在同步块中而仍然持有锁吗?

是的,它应该持有锁。调用 notify/notifyAll 方法的线程必须持有锁,并且将继续持有锁,直到正常离开同步块或发生异常:

  • 如果块的执行正常完成,则监视器将被解锁,同步语句也将正常完成。
  • 如果块的执行突然中止,则监视器将被解锁,同步语句也将因相同原因突然中止。
    JLS-14.19
notify/notifyAll 方法可以改变等待在此监视器上的线程1的状态,从 State.WAITING 变为 State.RUNNABLE。当这些线程被唤醒后,它们可以参与获取锁。
接近监视器时,其中一些线程2可能会获得 STATE.BLOCKED 状态,并等待另一个线程释放锁。请注意,这不需要持有锁的线程发出任何通知。

被唤醒的线程在当前线程释放此对象上的锁之前无法继续执行。 被唤醒的线程将像任何其他正在积极竞争对此对象进行同步的线程一样进行竞争;例如,被唤醒的线程在成为下一个锁定此对象的线程方面没有可靠的优势或劣势。
docs


1. 在notify的情况下,它是一个任意选择的单个线程。
2. 或者全部线程——如果通知的线程保持监视器。


如果没有notifyAll()而线程跳出同步块,会发生什么? 当线程跳出同步块时锁定是否被释放?即使锁可能由持有线程(通过跳出同步块并且不需要任何notify()的情况下)释放,等待wait()的线程是否需要被其他线程通过notify()通知? - CuriousMind
1
@CuriousMind 锁将被释放,但未被通知的等待线程不会切换到运行状态(它们将保持在“State.WAITING”中)。您可以使用其中一个超时版本,例如wait(long timeout, int nanos),以确保等待线程不会永远等待。 - Hulk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接