Java 线程执行次数太多,即使I';我在用锁

Java 线程执行次数太多,即使I';我在用锁,java,multithreading,thread-safety,race-condition,Java,Multithreading,Thread Safety,Race Condition,我正在为一个用于模拟仓库的练习(类似于生产者-消费者问题)开发一个多线程应用程序,但是我在程序中遇到了一些问题,增加消费者线程的数量会使程序以意外的方式运行 守则: 我正在创建一个名为buyer的生产者线程,其目标是从仓库中准确订购10份订单。为此,他们有一个名为warehouse的共享对象,买家可以在该对象上下订单,然后将订单存储在共享对象的缓冲区中。在此之后,买家会睡上一段时间,直到它再次尝试或所有包装都已购买。执行此操作的代码如下所示: public void run() { //

我正在为一个用于模拟仓库的练习(类似于生产者-消费者问题)开发一个多线程应用程序,但是我在程序中遇到了一些问题,增加消费者线程的数量会使程序以意外的方式运行

守则: 我正在创建一个名为buyer的生产者线程,其目标是从仓库中准确订购10份订单。为此,他们有一个名为warehouse的共享对象,买家可以在该对象上下订单,然后将订单存储在共享对象的缓冲区中。在此之后,买家会睡上一段时间,直到它再次尝试或所有包装都已购买。执行此操作的代码如下所示:

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()。它使用以下代码执行此操作:

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];
            }