【问题标题】:In java If a thread calls notify() before wait(), how does this not cause the second thread to enter the block before the first goes to wait state?在java中如果一个线程在wait()之前调用notify(),这如何不导致第二个线程在第一个进入等待状态之前进入块?
【发布时间】:2016-10-07 23:59:34
【问题描述】:

所以我编写了以下代码来运行一个线程来打印偶数,而另一个线程使用相同的计数器变量来打印奇数。但是,我想知道是什么内部机制导致 notify() 仅在 wait() 之后触发,即使 notify() 在 wait() 之前调用并且之间有操作。

与 T1 一样,发出 notify() 但仅在迭代 10000 次后才进入 wait() 状态。 t2 不应该在 notify 被触发后立即尝试获取锁,或者 notify() 按设计是否等待 wait() 被调用?

PS:我看到无论我将 notify() 调用放在哪里,只要我在 wait() 调用之前编写它,它总是在 wait() 之后调用。

import java.io.IOException;
public class PrintThread implements Runnable {

int i = 0;
private final Object lock = new Object();

public void run() {

    synchronized (lock) {
        while (i <= 10) {

            System.out.println(Thread.currentThread().getName() + " i is " + i);
            i++;
            lock.notify();

            //Just to add some delay
            for(int i=0;i<10000;i++){}

            try {
                lock.wait();
            } catch (InterruptedException e) {
                System.out.println("Error");
            }

        }
        System.out.println(Thread.currentThread().getName() + " Exiting!");
    }
}}

还有驱动程序

public class PrintThreadDriver {
public static void main(String args[]) throws InterruptedException {
    PrintThread obj1 = new PrintThread();
    Thread t1 = new Thread(obj1);
    Thread t2 = new Thread(obj1);
    t1.start();
    t2.start();
}}

输出:

Thread-0 i is 0 Thread-1 i is 1 Thread-0 i is 2 Thread-1 i is 3 Thread-0 i is 4 Thread-1 i is 5 Thread-0 i is 6 Thread-1 i is 7 Thread-0 i is 8 Thread-1 i is 9 Thread-0 i is 10 Thread-1 Exiting!

我还看到其中一个线程继续处于等待状态。一旦计数器达到 10,关于如何终止它的任何想法?

【问题讨论】:

  • 什么错误?它通知正在等待的线程之一。可以有零个或多个。没关系。
  • @EJP 没有错误,只是想知道 notify 是如何不立即调用其他等待线程的。编辑添加更多细节。
  • 我指的是你标题中提到的错误。如果这不是您问题的一部分,那是什么意思?
  • @EJP 更正了标题?
  • 所以当我问'什么错误'并且你回答'没有错误'时,你真正的意思是'并发访问错误'?为什么不一开始就这么说呢?我现在的问题是您的标题中提到的“这个并发访问错误是什么”?我不知道Java中有这样的事情。您期望它发生的依据是什么?

标签: java multithreading concurrency


【解决方案1】:

对于锁定对象,您有 wait setblocked set

您的程序中发生了什么:

  1. 第一个线程启动,监视器被锁定(锁定被捕获)

  2. 第二个线程已启动,但由于监视器被锁定,第二个线程进入阻塞集。

  3. 第一个线程打印“Thread-0 i is 0”,执行操作并从锁中调用通知。什么都没有发生,因为 notify 从等待集中删除了一个随机线程。现在我们有一个空的等待集。第二个线程仍处于阻塞集中。

  4. 第一个线程在自旋锁中稍作等待,调用等待并进入等待集。监控器解锁,第二个线程取走监控器并开始工作,监控器被第二个线程取走。

  5. 第二个线程打印“Thread-1 i is 1”并调用 notify。第一个线程从等待集合中移除并进入阻塞集合,因为监视器仍然被第二个线程跟踪。第二个线程调用等待并进入等待集。显示器已解锁。

  6. 第一个线程在调用 lock.wait() 时获取监视器并从该行继续工作。

  7. 第一个线程继续循环并打印“Thread-0 i is 2”,然后调用 notify。第二个线程从等待集中移除并放入阻塞集中。第一个线程调用等待并进入等待集。显示器已解锁。

  8. 第二个线程在调用 lock.wait() 时获取监视器并从该行继续工作。

  9. 第二个线程继续循环并打印“Thread-1 i is 3”,然后调用 notify。第一个线程从等待集中移除并放入阻塞集中。第二个线程调用等待并进入等待集。显示器已解锁。

  10. 在 i

现在,让我们来描述最后一次迭代:

  1. i=10。第二个线程处于等待设置中。监视器被第一个线程阻塞。第一个线程继续循环并打印“Thread-0 i is 10”。然后第一个线程调用通知,第二个线程从等待集合中移除并进入阻塞集合。第一个线程调用等待并进入等待集。显示器已解锁。

  2. 第二个线程在调用 lock.wait() 时获取监视器并从该行继续工作。

  3. 第二个线程退出循环,因为 i==11,打印“线程 1 退出!”并解锁显示器。第二个线程结束了。

  4. 监视器已解锁,但第一个线程仍在等待设置中,它将永远存在,因为没有人会调用通知! (虚假唤醒情况除外)

由于线程第一个线程不是恶魔程序不会完成。 对于您的情况,只需在循环之后复制 lock.notify(); 即可从等待集中删除第一个线程并让他完成。

您可以在此处找到下文所述行为所需的所有证明和确认:https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

PS:Notify 永远不会立即调用其他等待线程。它只会从Wait Set中移除一个线程,然后Operating System会调用这个线程!如果线程试图进入已经被另一个线程占用的锁定监视器(同步块),它将被放置到阻塞集中,直到它可以获得该监视器上的锁。

【讨论】:

  • 您的引文中没有关于“阻塞集”的内容。
  • @EJP 17.1 同步。 “......一次只有一个线程可以持有监视器上的锁。!!!任何其他试图锁定该监视器的线程都被阻塞,直到他们可以获得该监视器上的锁。”因此,阻塞集只是一个抽象概念,意味着一个或多个线程在监视器的入口处被阻塞。我使用术语“阻塞集”更容易理解,因为如果有多个线程被阻塞,只有一个随机线程(由操作系统选择)在解锁后将获得监视器。 Blocked Set of threads 意味着一定数量的被阻塞的线程,对于监视器来说没有顺序。
【解决方案2】:

在java中如果一个线程在wait()之前调用notify(),这怎么不导致第二个线程在第一个进入等待状态之前进入block呢?

因为第一个线程在调用wait() 或退出synchronized 块之前仍然持有锁。来自Javadoc

被唤醒的线程将无法继续,直到当前 线程放弃对该对象的锁定。

【讨论】:

  • 我的理解是,notify 意思是尝试进入Critical 部分,但是假设Notify 和wait() 之间有一些操作,为什么另一个线程等待当前线程退出临界区,这是否也意味着一旦调用 notify,其中一个线程将退出睡眠状态并不断检查锁以查看其何时可用,那么它是否会消耗 CPU 周期?跨度>
  • @Sameer 你的问题没有意义。另一个线程在wait()。这就是它在等待的原因。如果在调用notify() 时它不在wait() 中,则不会收到通知。 wait() 几乎肯定不会用自旋锁实现。
【解决方案3】:

Wait 表示当前持有锁的线程将把锁交给线程池中的另一个线程,下一个获得锁的线程(由线程调度器决定)将完成任务并通知给线程池中的另一个线程上一个带有 notify() 的线程。在 notify() 之后,给锁的等待线程将恢复其任务。

【讨论】:

  • Q1:假设线程0先进入,此时线程1会发生什么,会自动进入等待状态吗? Q2:如果线程0是第一个进入的线程,在顺序流触发wait()之前仍然调用notify(),这个notify()调用会发生什么?
  • Q1:如果线程 0 先进入,那么线程 1 在线程池中。如果线程 0 调用静态 wait() 方法,那么线程 0 将锁给下一个线程(下一个线程只能由线程调度程序确定,除非你给它优先级)。 Q2:notify() 将在线程 2 获得锁时调用。考虑到您知道线程间通信是如何发生的,那么只有单个线程能够获得该定义的同步方法/块的锁。
  • 当第一个进入同步块时,第一个线程遇到 notify() 会发生什么,是否被忽略?
  • 同步块/方法中的其他线程无法通知当前线程,因为在同步块/方法中只允许当前线程执行,因此我们使用等待,将锁给其他线程在线程池中。
  • 这里没有'线程池',接收到notify()的线程不会'用notify()通知回上一个线程'。
猜你喜欢
  • 2016-03-21
  • 1970-01-01
  • 1970-01-01
  • 2012-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多