我正在用线程在Java中做厨师,面包和客户场景。所以基本上厨师制作面包,顾客吃它,厨师制作更多。最大值为20.当有20个时,厨师停止制作面包。当没有剩下时,顾客停止进食。但是每当我使用notifyall时,它会在顾客吃掉它之前等待四秒钟(应该再制作3个面包)。 这是在Chef类中运行的代码(实现runnable)
public void run(){
int id = 0;
while(true){
if(Basket.breadList.size() == 20){
synchronized(Basket.breadList){
try {
Basket.breadList.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Bread bread = new Bread(id);
System.out.println("Bread " + id + " had just been made. ");
synchronized(Basket.breadList){
Basket.breadList.notifyAll();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
id++;
}
}
}
以下是客户的代码:
public void run(){
int id;
while(true){
if(Basket.breadList.size() == 0){
synchronized(Basket.breadList){
try {
Basket.breadList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
id = Basket.breadList.get(Basket.breadList.size()-1).id;
Basket.breadList.remove(Basket.breadList.size()-1);
System.out.println("Bread " + id + " had just been eaten. ");
synchronized(Basket.breadList){
Basket.breadList.notifyAll();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
以下是控制器中的代码:
public static void main(String[] args) {
Chef chef = new Chef();
Customer customer = new Customer();
Thread t1 = new Thread(chef);
Thread t2 = new Thread(customer);
t1.start();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
Basket.breadList只是一个面包的arraylist。
请帮忙。非常感谢!
答案 0 :(得分:1)
在您的客户吃任何面包后,它总是等待4秒。没有什么可以阻止这种等待(通常应该有一些if ()
。)
规则:从不睡觉()无条件地,除非你绝对确定这是应该如何。你总是睡觉(),因为没有别的事情可以完成,可能不会有一段时间。所以你需要检查是否有。
另外,notifyAll()通常在之前的之后立即完成,以便其他线程可以处理。 规则:在您放入内容后立即调用容器上的notifyAll()。
目前还不清楚,在哪些代码以及何时Chef将面包添加到篮子中。我假设面包在自己的构造函数中添加自己 - 如果是这样,它是一个反模式。保持面包简单健康,这样味道会更好。让厨师做好工作。如果揉捏和烘烤自己的面包爬进篮子里,我会感到很害怕。
一般来说,尝试完全按照现实世界中的角色来编写代码。厨师通知客户篮子不是空的吗?什么时候?当篮子是空的时,客户会通知厨师吗?什么时候?他们中的任何一个什么时候等待?
答案 1 :(得分:0)
您的代码没有有效锁定,因此当一个线程正在工作时,另一个线程可以篡改数据。这是一个示例,显示了一个更好的方法让背部烤一条面包,然后等待面包供应量最大化,然后将面包添加到库存中:
try {
while (true) {
Thread.sleep(4000);
Bread bread = new Bread(id++);
synchronized(Basket.breadList) {
while (Basket.breadList.size() == 20) {
Basket.breadList.wait();
}
Basket.breadList.add(bread);
Basket.breadList.notifyAll();
}
}
} catch (InterruptedException e) {
}
wait方法释放锁,然后在它退出之前重新获取锁。由于此示例在检查和操作时保持锁定,因此一旦内部while循环退出,则确定breadList包含少于20个项目。客户应该被类似地重写。
这个版本在持有锁的同时循环等待,检查等待中出现的之后的条件,因为当你的线程没有锁时,某些东西可能已经改变了。
另外,仅仅因为你的线程醒来并不意味着你收到了通知。 wait方法可以在没有收到通知的情况下退出。有关如何使用wait和notify,请参阅the Oracle tutorial。
你的线程知道列表大小的唯一方法是在持有锁的同时检查它,否则它可能会改变你(另一个线程可以换入并在你的检查之间改变一些东西以及你采取的任何行动,导致第一个线程基于可能陈旧的信息做出决定。
同样,您的客户不应该从共享列表中删除某些内容,而无需对其进行锁定。 ArrayList不是线程安全的,您也不希望状态在删除项目和发送通知之间进行更改。如果要从列表中删除某些内容然后想要执行通知,请获取锁定,然后执行删除操作并在保持锁定的同时一起通知。
在睡觉时不要握住锁,这对于表现来说毫无意义且不好。在这个例子中,如果你想模拟需要时间来创建面包,那么在调用面包构造函数之前睡眠就会更好。
您的代码吞下InterruptedException的方式并不能帮助您的线程在被中断后实际干净地退出。如果你在while(true)循环之外捕获InterruptedException,那么线程将通过实际退出其工作并终止来响应中断。
面包师和顾客不应该负责锁定,这让人感到困惑,并且更难理解多线程如何适用于现实生活中。在这里使用队列,使面包师成为生产者,使客户成为消费者。您已经拥有了一个共享数据结构arrayList,但是您选择了一个不是线程安全但无法阻塞的数据结构,共享数据结构需要负责保护自己的完整性。这样,角色更加清晰,锁定,等待和通知发生在共享数据结构中而不是线程中。使用java.util.concurrent包中的阻塞队列在这里是一个不错的选择,或者如果你想要体验,可以自己编写,一旦你阅读了链接的教程,它应该是可行的。使用单独的队列后,面包师的run方法变为:
public void run() {
try {
int id = 0;
while (true) {
Thread.sleep(4000);
queue.put(new Bread(id++));
}
} catch (InterruptedException e) {
}
}
虽然队列的put方法是
public synchronized void put(Bread b) throws InterruptedException {
while (breadList.size() == 20) {
wait();
}
breadList.add(b);
notifyAll();
}
假设breadList是队列的私有实例成员。