Java 线程执行次数太多,即使I';我在用锁
我正在为一个用于模拟仓库的练习(类似于生产者-消费者问题)开发一个多线程应用程序,但是我在程序中遇到了一些问题,增加消费者线程的数量会使程序以意外的方式运行 守则: 我正在创建一个名为buyer的生产者线程,其目标是从仓库中准确订购10份订单。为此,他们有一个名为warehouse的共享对象,买家可以在该对象上下订单,然后将订单存储在共享对象的缓冲区中。在此之后,买家会睡上一段时间,直到它再次尝试或所有包装都已购买。执行此操作的代码如下所示:Java 线程执行次数太多,即使I';我在用锁,java,multithreading,thread-safety,race-condition,Java,Multithreading,Thread Safety,Race Condition,我正在为一个用于模拟仓库的练习(类似于生产者-消费者问题)开发一个多线程应用程序,但是我在程序中遇到了一些问题,增加消费者线程的数量会使程序以意外的方式运行 守则: 我正在创建一个名为buyer的生产者线程,其目标是从仓库中准确订购10份订单。为此,他们有一个名为warehouse的共享对象,买家可以在该对象上下订单,然后将订单存储在共享对象的缓冲区中。在此之后,买家会睡上一段时间,直到它再次尝试或所有包装都已购买。执行此操作的代码如下所示: public void run() { //
public void run() {
//Run until the thread has bought 10 packages, this ensures the thread
//will eventually stop execution automatically.
while(this.packsBought < 10) {
try {
//Sleep for a random amount of time between 1 and 50
//milliseconds.
Thread.sleep(this.rand.nextInt(49) + 1);
//Catch any interruptExceptions.
} catch (InterruptedException ex) {
//There is no problem if this exception is thrown, the thread
//will just make an order earlier than planned. that being said
//there should be no manner in which this exception is thrown.
}
//Create a new order.
Order order = new Order(this.rand.nextInt(3)+ 1,
this,
this.isPrime);
//Set the time at which the order was placed as now.
order.setOrderTime(System.currentTimeMillis());
//place the newly created order in the warehouse.
this.warehouse.placeOrder(order);
}
//Notify the thread has finished execution.
System.out.println("Thread: " + super.getName() + " has finished.");
}
public void placeOrder(Order order) {
try{
//halt untill there are enough packs to handle an order.
this.notFullBuffer.acquire();
//Lock to signify the start of the critical section.
this.mutexBuffer.lock();
//Insert the order in the buffer depending on prime status.
if (order.isPrime()) {
//prime order, insert behind all prime orders in buffer.
//Enumerate all non prime orders in the list.
for (int i = inPrime; i < sizeOrderList - 1; i++) {
//Move the non prime order back 1 position in the list.
buffer[i + 1] = buffer[i];
}
// Insert the prime order.
buffer[inPrime++] = order;
} else {
//No prime order, insert behind all orders in buffer.
buffer[inPrime + inNormal++] = order;
}
//Notify the DispatchWorkers that a new order has been placed.
this.notEmptyBuffer.release();
//Catch any InterruptException that might occure.
} catch(InterruptedException e){
//Even though this isn't expected behavior, there is no reason to
//notify the user of this event or to preform any other action as
//the thread will just return to the queue before placing another
//error if it is still required to do so.
} finally {
//Unlock and finalize the critical section.
mutexBuffer.unlock();
}
}
订单由充当消费者线程的工人消费。线程本身包含非常简单的代码循环,直到处理完所有订单。在这个循环中,一个不同的函数handleOrder()对处理缓冲区中单个订单的同一仓库对象调用code>。它使用以下代码执行此操作:
public void handleOrder(){
//Create a variable to store the order being handled.
Order toHandle = null;
try{
//wait until there is an order to handle.
this.notEmptyBuffer.acquire();
//Lock to signify the start of the critical section.
this.mutexBuffer.lock();
//obtain the first order to handle as the first element of the buffer
toHandle = buffer[0];
//move all buffer elementst back by 1 position.
for(int i = 1; i < sizeOrderList; i++){
buffer[i - 1] = buffer[i];
}
//set the last element in the buffer to null
buffer[sizeOrderList - 1] = null;
//We have obtained an order from the buffer and now we can handle it.
if(toHandle != null) {
int nPacks = toHandle.getnPacks();
//wait until the appropriate resources are available.
this.hasBoxes.acquire(nPacks);
this.hasTape.acquire(nPacks * 50);
//Now we can handle the order (Simulated by sleeping. Although
//in real live Amazon workers also have about 5ms of time per
//package).
Thread.sleep(5 * nPacks);
//Calculate the total time this order took.
long time = System.currentTimeMillis() -
toHandle.getOrderTime();
//Update the total waiting time for the buyer.
toHandle.getBuyer().setWaitingTime(time +
toHandle.getBuyer().getWaitingTime());
//Check if the order to handle is prime or not.
if(toHandle.isPrime()) {
//Decrement the position of which prime orders are
//inserted into the buffer.
inPrime--;
} else {
//Decrement the position of which normal orders are
//inserted into the buffer.
inNormal--;
}
//Print a message informing the user a new order was completed.
System.out.println("An order has been completed for: "
+ toHandle.getBuyer().getName());
//Notify the buyer he has sucsessfully ordered a new package.
toHandle.getBuyer().setPacksBought(
toHandle.getBuyer().getPacksBought() + 1);
}else {
//Notify the user there was a critical error obtaining the
//error to handle. (There shouldn't exist a case where this
//should happen but you never know.)
System.err.println("Something went wrong obtaining an order.");
}
//Notify the buyers that a new spot has been opened in the buffer.
this.notFullBuffer.release();
//Catch any interrupt exceptions.
} catch(InterruptedException e){
//This is expected behavior as it allows us to force the thread to
//revaluate it's main running loop when notifying it to finish
//execution.
} finally {
//Check if the current thread is locking the buffer lock. This is
//done as in the case of an interrupt we don't want to execute this
//code if the thread interrupted doesn't hold the lock as that
//would result in an exception we don't want.
if (mutexBuffer.isHeldByCurrentThread())
//Unlock the buffer lock.
mutexBuffer.unlock();
}
}
从handleOrder()代码>函数。我将整个输出放在一个文本文件中,删除所有没有通过这个println()添加的行代码>语句并计算行数,以了解已处理的订单数量。我希望这个值等于线程数乘以10,但情况通常不是这样。运行测试我注意到有时它确实有效并且没有问题,但有时一个或多个买家线程接受的订单比他们应该接受的多。有5个买家线程,应该有50个输出,但我可以得到50到60行(订单)
将线程数量增加到30会增加问题,现在我可以预期订单数量会增加50%,而一些线程最多会增加30个订单
做一些研究这被称为数据竞争,是由两个线程同时访问相同的数据,而其中一个线程写入数据引起的。这基本上改变了数据,使得另一个线程不能使用它期望使用的相同数据
我的尝试:
我坚信,reentrantlock
设计用于处理类似的情况,因为如果其他线程没有离开某段代码,它们应该阻止任何线程进入该段代码。placeOrder(订单订单)代码>和handleOrder()代码>功能使用此机制。因此,我假设我没有正确地实现这一点。下面是该项目的一个版本,它可以从一个名为。有没有人能看看上面解释的代码,告诉我我做错了什么
编辑
我注意到有一种方式买家可以下10多个订单,因此我将代码更改为:
/*
* The run method which is ran once the thread is started.
*/
public void run() {
//Run until the thread has bought 10 packages, this ensures the thread
//will eventually stop execution automatically.
for(packsBought = 0; packsBought < 10; packsBought++)
{
try {
//Sleep for a random amount of time between 1 and 50
//milliseconds.
Thread.sleep(this.rand.nextInt(49) + 1);
//Catch any interruptExceptions.
} catch (InterruptedException ex) {
//There is no problem if this exception is thrown, the thread
//will just make an order earlier than planned. that being said
//there should be no manner in which this exception is thrown.
}
//Create a new order.
Order order = new Order(this.rand.nextInt(3)+ 1,
this,
this.isPrime);
//Set the time at which the order was placed as now.
order.setOrderTime(System.currentTimeMillis());
//place the newly created order in the warehouse.
this.warehouse.placeOrder(order);
}
//Notify the thread has finished execution.
System.out.println("Thread: " + super.getName() + " has finished.");
}
/*
*线程启动后运行的run方法。
*/
公开募捐{
//运行直到线程购买了10个包,这确保了线程
//最终将自动停止执行。
for(packsbunded=0;packsbunded<10;packsbunded++)
{
试一试{
//在1到50之间随机睡眠一段时间
//毫秒。
Thread.sleep(this.rand.nextInt(49)+1);
//捕获任何中断异常。
}捕获(中断异常例外){
//如果抛出此异常,则线程
//只会比计划的更早下订单
//不应以任何方式引发此异常。
}
//创建新订单。
订单=新订单(此.rand.nextInt(3)+1,
这
这是(i.isPrime);
//将下订单的时间设置为现在。
setOrderTime(System.currentTimeMillis());
//将新创建的订单放入仓库。
本.仓库.下订单(订单);
}
//通知线程已完成执行。
System.out.println(“线程:“+super.getName()+”已完成”);
}
在买家run()中代码>功能,但我仍然得到一些线程,放置超过10个订单。我还删除了在handleOrder()中购买的包装数量的更新代码>功能,因为现在不需要了。是Test.java的更新版本(所有类都集中在一起以便于执行),这里似乎存在不同的问题。我相信您可能在追逐幽灵。我不完全清楚为什么你看到的产出比你预期的要多,但下订单的数量似乎是有序的。请允许我澄清:
我在仓库
类中添加了一个映射
,以映射每个线程下的订单数量:
private-Map-ordersPlaced=new-TreeMap();
//为简洁起见省略了代码
公共无效订单(订单)
{
尝试
{
//停止,直到有足够的包装来处理订单。
这个.notFullBuffer.acquire();
//锁定表示临界段的开始。
this.mutexBuffer.lock();
ordersPlaced.merge(Thread.currentThread().getName(),1,Integer::sum);
//方法的其余部分
}
然后,我在main
方法中添加了一个for循环,以执行代码100次,并在每次迭代结束时添加了以下代码:
warehouse.ordersPlaced.forEach((线程,订单)->System.out.printf(“%s-%d%n”,线程,订单));
我在lambda表达式中放置了一个断点,条件是orders!=10
。在我执行的100多次运行中从未触发过此条件。据我所知,您的代码正在按预期工作。我已将nWorkers
和nBuyers
增加到100以确保
我相信您正确地使用了ReentrantLock
,我同意这一点
/*
* The run method which is ran once the thread is started.
*/
public void run() {
//Run until the thread has bought 10 packages, this ensures the thread
//will eventually stop execution automatically.
for(packsBought = 0; packsBought < 10; packsBought++)
{
try {
//Sleep for a random amount of time between 1 and 50
//milliseconds.
Thread.sleep(this.rand.nextInt(49) + 1);
//Catch any interruptExceptions.
} catch (InterruptedException ex) {
//There is no problem if this exception is thrown, the thread
//will just make an order earlier than planned. that being said
//there should be no manner in which this exception is thrown.
}
//Create a new order.
Order order = new Order(this.rand.nextInt(3)+ 1,
this,
this.isPrime);
//Set the time at which the order was placed as now.
order.setOrderTime(System.currentTimeMillis());
//place the newly created order in the warehouse.
this.warehouse.placeOrder(order);
}
//Notify the thread has finished execution.
System.out.println("Thread: " + super.getName() + " has finished.");
}
//Enumerate all non prime orders in the list.
for (int i = inPrime; i < sizeOrderList - 1; i++) {
//Move the non prime order back 1 position in the list.
buffer[i + 1] = buffer[i];
}
buffer[1] = buffer[0] //normal order in 0 get copied to 1
buffer[2] = buffer[1] //now its in 1, so it gets copied to 2
buffer[3] = buffer[2] //now its in 2 too, so it gets copied to 3
....
//Enumerate all non prime orders in the list.
for (int i = (sizeOrderList-1); i > inPrime; i--) {
//Move the non prime order back 1 position in the list.
buffer[i] = buffer[i-1];
}