问题描述
我正在用Java在Java中做厨师,面包和客户场景.因此,基本上厨师会做面包,顾客会吃,而厨师会做更多.最大值是20.厨师在20时停止制作面包.顾客在没有空的情况下停止进餐.但是每次我使用notifyall时,它都要等四秒钟,然后顾客才能吃掉它(假设还要再制作3个面包).这是在Chef类中运行的代码(可运行的实现)
I am doing the chef, bread, and customer scenario in Java with thread. So basically the chef makes a bread, the customer eats it, the chef makes more. The maximum is 20. The chef stop making bread when there are 20. The customer stop eating when there is none left. But everytime I use notifyall, it wait four seconds before the customer eat it(supposed to make 3 more breads).Here is the code for run in the Chef class(implements 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只是面包的数组列表.
Basket.breadList is just an arraylist of breads.
请帮助.非常感谢!
推荐答案
您的代码未有效锁定,因此,当一个线程正在工作时,另一个线程可以篡改数据.这是一个示例,它显示了一种更好的方法,供支持者烘焙一条面包,然后等待面包供应量最大化,然后再将其添加到库存中.
Your code is not locking effectively, so that while one thread is working the other can tamper with the data. Here's an example showing a better way for the backer to bake a loaf of bread, then wait while the bread supply is maxed out, then add the loaf to the inventory:
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个.应该以类似的方式重写客户.
The wait method releases the lock, then re-acquires the lock before it can exit. Since this example holds the lock while it is checking and acting, once the inner while loop is exited from it is certain that the breadList contains less than 20 items. The customer should be rewritten similarly.
此版本在按住锁的同时循环等待,检查从等待中出现的条件之后,因为在您的线程没有锁的情况下,某些内容可能已更改.
This version waits in a loop while holding the lock, checking the condition after emerging from the wait, because something may have changed while your thread didn't have the lock.
此外,仅仅是因为您的线程醒了并不意味着您会收到通知. wait方法可以在未收到通知的情况下退出.有关如何使用等待和通知的信息,请参见 Oracle教程.
Also, just because your thread woke up doesn't mean you got a notification. The wait method can exit without having received a notification. See the Oracle tutorial for how to use wait and notify.
您的线程知道列表的真正大小的唯一方法是在按住锁的同时对其进行检查,否则它可能会在您身上发生变化(另一个线程可以交换并在两次检查之间更改某些内容)以及您采取的任何措施),导致第一个线程基于可能过时的信息做出决定.
The only way for your thread to know what the size of the list really is is to check it while holding the lock, otherwise it could be changing on you (the other thread can be swapped in and change something in between your check and whatever action you take), resulting in the first thread basing its decisions on possibly stale information.
同样,您的客户不应在未锁定共享的情况下从共享列表中删除某些内容. ArrayList不是线程安全的,您也不希望在删除项目和发送通知之间更改状态.如果要从列表中删除某项,然后要执行通知,请获取该锁,然后进行删除并在按住该锁的同时进行通知.
Likewise your customer shouldn't be removing something from the shared list without holding the lock on it. ArrayList is not threadsafe, also you don't want the state to change in between removing an item and sending the notification. If you are removing something from the list and then want to perform a notification, acquire the lock, then do the removal and notify together while holding the lock.
睡觉时不要锁,这毫无意义,而且对性能不利.在此示例中,如果您想模拟需要时间来创建面包,那么最好在调用Bread构造函数之前进行睡眠.
Don't hold a lock while sleeping, it's pointless and bad for performance. It would be better in this example, if you want to simulate needing time to create bread, for the sleep to come before the call to the Bread constructor.
您的代码吞下InterruptedException的方式并不能帮助您的线程在被中断后实际上干净地退出.如果您在while(true)循环之外捕获了InterruptedException,则该线程将通过实际退出其工作并终止来对中断做出响应.
The way your code swallows InterruptedException doesn't help your thread actually exit cleanly once interrupted. If you catch the InterruptedException outside the while (true) loop then the thread will respond to interruption by actually quitting its work and terminating.
面包师和客户不应负责锁定,这会造成混乱,并使您更难于理解多线程如何适用于现实生活中的情况.在这里使用队列,使面包师成为生产者,使顾客成为消费者.您已经有一个共享的数据结构,即arrayList,但是您选择了一个不是线程安全的并且不能进行阻塞的数据结构,该共享数据结构需要负责保护其自身的完整性.这样,角色就更加清晰了,锁定,等待和通知发生在共享数据结构中,而不是线程中.在这里,使用java.util.concurrent包中的阻塞队列将是一个不错的选择,或者如果您希望自己编写体验,也可以自己编写,阅读链接的教程后,它应该是可行的.使用单独的队列后,面包师的run方法将变为:
The baker and customer should not be in charge of locking, it's confusing and makes it harder to understand how multithreading is applicable to real-life situations. Use a queue here, making the baker a producer and the customer a consumer. You have a shared data structure already, the arrayList, but you chose a data structure that isn't threadsafe and can't do blocking, the shared data structure needs to be in charge of protecting its own integrity. That way the roles are much clearer, with the locking, waiting, and notifying taking place in the shared data structure and not in the threads. Using a blocking queue from the java.util.concurrent package would be a good choice here, or write your own if you want the experience, it should be do-able once you read the linked tutorial. Once you use a separate queue the run method for the baker becomes:
public void run() {
try {
int id = 0;
while (true) {
Thread.sleep(4000);
queue.put(new Bread(id++));
}
} catch (InterruptedException e) {
}
}
而队列的put方法将是
while the queue's put method would be
public synchronized void put(Bread b) throws InterruptedException {
while (breadList.size() == 20) {
wait();
}
breadList.add(b);
notifyAll();
}
假设BreadList是队列的私有实例成员.
assuming that breadList is a private instance member of the Queue.
这篇关于等待并通知所有其他线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!