我正尝试使用ReentrantLock
来实现消费者生产者,如下所示:
class Producer implements Runnable {
private List<String> data;
private ReentrantLock lock;
Producer(List<String> data,ReentrantLock lock)
{
this.data = data;
this.lock = lock;
}
@Override
public void run() {
int counter = 0;
synchronized (lock)
{
while (true)
{
if ( data.size() < 5)
{
counter++;
data.add("writing:: "+counter);
}
else
{
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Consumer implements Runnable{
private List<String> data;
private ReentrantLock lock;
Consumer(List<String> data,ReentrantLock lock)
{
this.data = data;
this.lock = lock;
}
@Override
public void run() {
int counter = 0;
synchronized (lock)
{
while (true)
{
if ( data.size() > 0)
{
System.out.println("reading:: "+data.get(data.size()-1));
data.remove(data.size()-1);
}
else
{
System.out.println("Notifying..");
lock.notify();
}
}
}
}
}
public class ProducerConsumer {
public static void main(String[] args) {
List<String> str = new LinkedList<>();
ReentrantLock lock= new ReentrantLock();
Thread t1 = new Thread(new Producer(str,lock));
Thread t2 = new Thread(new Consumer(str,lock));
t1.start();
t2.start();
}
}
因此,它只向列表写入一次,然后消费者无限期地等待。为什么会这样呢?生产者为什么不获取锁?
答案 0 :(得分:2)
@Antoniossss是正确的。您没有正确使用ReentrantLock,而只能将其替换为Object。如果您想改用ReentrantLock(这是最新的),那么我会建议:
package Multithreading;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class Producer implements Runnable{
private List<String> data;
private ReentrantLock lock;
Producer(List<String> data,ReentrantLock lock)
{
this.data = data;
this.lock = lock;
}
@Override
public void run() {
int counter = 0;
while (true)
{
try {
lock.lock();
if ( data.size() < 5)
{
counter++;
data.add("writing:: " + counter);
}
}finally {
lock.unlock();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
private List<String> data;
private ReentrantLock lock;
Consumer(List<String> data,ReentrantLock lock)
{
this.data = data;
this.lock = lock;
}
@Override
public void run() {
int counter = 0;
while (true)
{
try {
lock.lock();
if ( data.size() > 0)
{
System.out.println("reading:: "+data.get(data.size()-1));
data.remove(data.size()-1);
}
}finally {
lock.unlock();
}
try
{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class ProducerConsumer {
public static void main(String[] args) {
List<String> str = new LinkedList<>();
ReentrantLock lock= new ReentrantLock();
Thread t1 = new Thread(new Producer(str,lock));
Thread t2 = new Thread(new Consumer(str,lock));
t1.start();
t2.start();
}
}
我删除了通知调用,但是如果您真的需要一次让一个线程进入,请使用lock.notify()
答案 1 :(得分:1)
您使用lock()
和unlock()
而不是ReentrantLock
获取并释放synchronized
。
正如Antoniossss指出的那样,这是一个死锁,其中一个线程正在等待锁,而另一个线程永远不会放弃(而两个线程都试图在该锁上进行协调)。这是一种解决方案:
import static java.util.Objects.requireNonNull;
import java.util.LinkedList;
import java.util.List;
class Producer implements Runnable {
private final List<String> data;
Producer(List<String> data) {
this.data = requireNonNull(data);
}
@Override
public void run() {
int counter = 0;
while (true) {
synchronized (data) {
if (data.size() < 5) {
counter++;
data.add("writing:: " + counter);
} else {
try {
data.wait();
} catch (InterruptedException e) {
return;
}
}
}
}
}
}
class Consumer implements Runnable {
private final List<String> data;
Consumer(List<String> data) {
this.data = requireNonNull(data);
}
@Override
public void run() {
while (true) {
synchronized (data) {
if (data.size() > 0) {
System.out.println("reading:: " + data.get(data.size() - 1));
data.remove(data.size() - 1);
}
data.notify();
}
}
}
}
public class ProducerConsumer {
public static void main(String[] args) {
List<String> data = new LinkedList<>();
Thread t1 = new Thread(new Producer(data));
Thread t2 = new Thread(new Consumer(data));
t1.start();
t2.start();
}
}
我们已经取消了锁定对象,并在列表本身上进行了同步。生产者在while循环内进行同步,其效果是,一旦列表中至少有5个项目,生产者将等待,将列表中的监视器让出。
使用者也要在循环内部进行同步,这是至关重要的,因为在您的代码中,一旦获得监视器,它就永远不会放弃监视器。实际上,如果您首先启动了消费者(或者很不幸),那么根本不会产生任何东西。离开同步块或方法时,或者当在其上持有监视器wait()
s的线程释放监视器时,但是notify()
和notifyAll()
不会释放监视器。
如果有任何内容,消费者读取最后一个项目,然后立即通知生产者并释放锁。两个注意事项:
首先,尚不清楚您期望商品的订购顺序。您是否希望生产者生产5,消费者消耗5,依此类推,还是您希望5只是一个限制,以至于无法形成积压太大(这很好,这称为背压),但是消费者只要有货就急切地消费它们?此实现将执行后者。
第二,消费者在发布列表后立即尝试获取列表中的监视器。这是<忙>等待的一种形式,消费者和生产者随后竞相获得锁定,这可能是消费者经常赢得这场比赛,一旦列表为空,这将变得毫无意义。在Java 9或更高版本中,将调用onSpinWait
放在同步块外部但在使用者的while循环内部可能是明智的。在早期版本中,yield
可能是合适的。但是在我的测试中,任何一个代码都可以正常工作。
Antoniossss提出了另一个建议,即使用LinkedBlockingQueue,但是目前的代码始终采用最后一项,而使用队列将改变这种行为。取而代之的是,我们可以使用双端队列(双端队列),将项目放在最后并从末尾取出。看起来像这样:
import static java.util.Objects.requireNonNull;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
class Producer implements Runnable {
private final BlockingDeque<String> data;
Producer(BlockingDeque<String> data) {
this.data = requireNonNull(data);
}
@Override
public void run() {
int counter = 0;
while (true) {
counter++;
try {
data.put("writing:: " + counter);
} catch (InterruptedException e) {
break;
}
}
}
}
class Consumer implements Runnable {
private final BlockingDeque<String> data;
Consumer(BlockingDeque<String> data) {
this.data = requireNonNull(data);
}
@Override
public void run() {
while (true) {
try {
System.out.println("reading:: " + data.takeLast());
} catch (InterruptedException e) {
break;
}
}
}
}
public class ProducerConsumer {
public static void main(String[] args) {
BlockingDeque<String> data = new LinkedBlockingDeque<>(5);
Thread t1 = new Thread(new Producer(data));
Thread t2 = new Thread(new Consumer(data));
t1.start();
t2.start();
}
}
因为LinkedBlockingDeque
是并发数据结构,所以我们不需要任何同步块或在这里等待或通知。我们可以简单地尝试从双端队列中put
和takeLast
开始,如果双端队列已满或为空,它将阻塞。此双端队列的容量为5,因此,如果生产者比原始生产者领先那么远,它将对生产者施加反压。
但是,没有什么可以阻止生产者以消费者可以消费的速度来生产要素,这意味着第一个要素可能必须等待任意长时间才能被消费。我不清楚这是否是您的代码的意图。您可以通过以下方式实现此目的:通过再次引入wait()
和notify()
,使用Semaphore
或其他方式,但我将其保留下来,因为目前尚不清楚你甚至想要它。
关于InterruptedException
的最后一条笔记。如果有人在线程上调用interrupt()
,就会发生这种情况,但是唯一保留对生产者线程和使用者线程的引用的对象是main()
方法,它永远不会中断它们。因此,异常不应在此处发生,但是如果发生某种情况,我只需让生产者或消费者退出。在更复杂的情况下,如果线程处于睡眠状态或处于阻塞方法中(甚至在外部,如果它显式地对其进行检查),则可以将中断线程用作发信号的一种方式,但是我们不在此使用它。>
答案 2 :(得分:1)
您只想Producer
不间断地产生值,而Consumer
不间断地消耗这些值并最终等待产生值,因为没有值,请在同步和锁定时跳过并使用LinkedBlockingQueue
只需简单地生成:
queue.put(value)
而在消费者中,
value=queue.take();
瞧,消费者将获取任何值,并等待值队列为空。
关于您的代码:
ReentrantLock
。您可以将其替换为new Object
,并且输出将相同。 wait
和notify
是Object
的方法。您只能设法产生单一价值的原因在于您的消费者。实际上,您有这样的东西:
synchronized(lock){ // aquire lock's monitor
while(true){
lock.notify();
}
} // release lock's monitor - never doing that, thread never leaves this block
这里的问题是您执行从不离开同步块。因此,您正在调用notify
,但没有通过退出lock
块来释放synchronized
监视器。生产者已被“通知”,但要继续执行,必须重新获得lock
的监视器-但可以这样做,因为消费者从不释放它。这里几乎是经典的僵局。
您可以想象房间里有几个人,只有一个拿着棍子的人可以说话。因此,第一个使用syncronized(stick)
坚持下去。做它必须做的事情,并决定必须wait
给其他人,因此他打电话给wait
,然后将棒子传回。现在,第二人称可以说话,做他的工作,并决定给他棍子的那个人现在可以继续。他打电话给notify
-现在他必须离开synchronized(stick)
方块来传递球棒。如果他不这样做,那么第一人称无法继续进行-这就是您的情况。
答案 3 :(得分:1)
我想指出您犯的两个错误:
ReentrantLock
。每个对象都可以用于内部锁,因此无需查找特定的Lock
类。由于每个synchronized
块都受一个方法限制,并且您不需要any enhanced means,因此ReentrantLock
在这里是多余的。
synchronized
块的位置不正确。输入同步块后,除非离开同步块,否则任何人都不能进入该块。显然,您永远不会因为while(true)
而退出它。
我建议您删除ReentrantLock
,并在data
上进行同步。
@Override
public void run() {
int counter = 0;
while (true) {
synchronized (data) {
if (data.size() < 5) {
data.add("writing:: " + ++counter);
} else {
try {
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// we are out of the synchornized block here
// to let others use data as a monitor somewhere else
}
}