在java中使用带有“.wait”和“.notify”的“synchronized”代码块

mbskvtky  于 2021-06-27  发布在  Java
关注(0)|答案(2)|浏览(365)

我在学习 synchronized 代码块和 .wait() / .notify() 方法,很难理解它们在生产者-消费者设置中的交互方式。将下面类的同一示例传递给两个线程;一个线程运行producer方法,另一个线程运行consumer方法。

private Queue<Integer> queue = new LinkedList<>();
    private Object lock = new Object();

    public void producer() throws InterruptedException {
        Random random = new Random();
        while (true) {

            synchronized(lock) {
                while (queue.size() > 10) {
                    lock.wait();
                }

                queue.add(random.nextInt(100));
                lock.notify();
            }

        }
    }

    public void consumer() throws InterruptedException {
        while (true) {

            synchronized(lock) {
                if (queue.isEmpty()) {
                    lock.wait();
                }

                int val = queue.remove();
                System.out.println(val + ": " + queue.size());
                lock.notify();
            }

        }
    }

}

在这里, synchronized 在同一个对象上,使得两个代码块中只有一个同时运行。假设producer线程赢得了比赛,将一个元素添加到队列中,并调用notify。此时,使用者线程将在 synchronized(lock) 在consumer函数中(由于 sycnhornized ). 一旦生产者线程退出其同步代码块,使用者线程将进入其同步代码块。现在,队列是非空的,因为生产者只是在通知之前放入了一些内容。使用者线程将移除它,调用notify,退出其块,此时生产者将获得锁,因为它现在一直在等待 synchronized(lock) 生产函数中的行。三个问题:
在我看来,好像我们在生产者和消费者之间交替,所以队列大小将在0和1之间波动。我错过了什么?
既然退出同步代码块释放了等待线程可以看到和获取的锁,那么为什么我们需要整个等待和通知机制呢?在我看来 notify 在我上面描述的过程中,没有做任何事情,因为一旦锁可用,另一个线程就会获取它并进入它的代码块。
lock.notify() 也醒来线程在等待 synchronized(lock) ?

f3temu5u

f3temu5u1#

呼叫notify,此时制作者将获得它
如果不完全正确,生产者将不会运行,直到消费者释放锁(调用 wait 或者退出 synchronized 块)-它不是由 notify .
文件 notify :
在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。
请检查通知的全部文件并等待

yv5phkfx

yv5phkfx2#

您正在看到线程不足的示例。
饥饿发生的一种方式是这样写循环:

while (true) {
    synchronized(lock) {
        ...
    }
}

问题是,线程在释放 lock 是的,它又锁上了。如果当前任何其他线程被阻塞,等待相同的锁,那么执行此循环的线程几乎肯定会赢得再次锁定它的竞争,因为执行循环的线程已经在运行,但另一个线程需要时间“唤醒”
在这种情况下,我们说另一个线程“饿死了”。
一些线程库提供了一个所谓的公平锁,它通过确保总是将锁授予等待时间最长的线程来避免饥饿。但是公平锁通常不是默认的,因为它们损害了设计更好的程序的性能,而在这些程序中,锁的竞争并不是那么激烈。
在您的示例中,饥饿并不是一场灾难,因为每个线程都调用 wait() 当它没有工作做的时候。释放锁并允许另一个线程运行。但它几乎迫使线程“轮流”:一个线程总是在睡觉,而另一个线程则在工作。你也可以把它写成一个单线程程序。
如果您的线程没有将任何锁锁定的时间超过绝对必要的时间,则更好:

while (true) {
    int val;
    synchronized(queue_lock) {
        if (queue.isEmpty()) {
            lock.wait();
        }

        val = queue.remove();
        queue_lock.notify();
    }
    System.out.println(val + ": " + queue.size());
}

在这里我移动了 println(...) 从同步块中调用(我还重新命名了你的名字 lock 变量来强调它的目的是保护队列。)
通过移动 random() 从同步块中调用。这样,您就有更多的机会让两个线程并行运行——生产者可以生产每一个新的东西,而消费者则同时处理它“消耗”的某些东西

相关问题