我在学习 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)
?
2条答案
按热度按时间f3temu5u1#
呼叫notify,此时制作者将获得它
如果不完全正确,生产者将不会运行,直到消费者释放锁(调用
wait
或者退出synchronized
块)-它不是由notify
.文件
notify
:在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。
请检查通知的全部文件并等待
yv5phkfx2#
您正在看到线程不足的示例。
饥饿发生的一种方式是这样写循环:
问题是,线程在释放
lock
是的,它又锁上了。如果当前任何其他线程被阻塞,等待相同的锁,那么执行此循环的线程几乎肯定会赢得再次锁定它的竞争,因为执行循环的线程已经在运行,但另一个线程需要时间“唤醒”在这种情况下,我们说另一个线程“饿死了”。
一些线程库提供了一个所谓的公平锁,它通过确保总是将锁授予等待时间最长的线程来避免饥饿。但是公平锁通常不是默认的,因为它们损害了设计更好的程序的性能,而在这些程序中,锁的竞争并不是那么激烈。
在您的示例中,饥饿并不是一场灾难,因为每个线程都调用
wait()
当它没有工作做的时候。释放锁并允许另一个线程运行。但它几乎迫使线程“轮流”:一个线程总是在睡觉,而另一个线程则在工作。你也可以把它写成一个单线程程序。如果您的线程没有将任何锁锁定的时间超过绝对必要的时间,则更好:
在这里我移动了
println(...)
从同步块中调用(我还重新命名了你的名字lock
变量来强调它的目的是保护队列。)通过移动
random()
从同步块中调用。这样,您就有更多的机会让两个线程并行运行——生产者可以生产每一个新的东西,而消费者则同时处理它“消耗”的某些东西