Java线程项目奇怪的行为,同时增加线程数

Posted

技术标签:

【中文标题】Java线程项目奇怪的行为,同时增加线程数【英文标题】:Java thread project weird behavior while increasing num of threads 【发布时间】:2019-01-20 08:47:51 【问题描述】:

我创建了一个用于研究目的的项目,该项目使用 Threads 模拟餐厅服务。有一个供厨师准备餐点的线程和另一个供服务员提供餐点的线程。当我用 1 名厨师和 5 名服务员对其进行测试时,效果很好。但是当我增加厨师的数量时,程序会无限期地运行。怎么了?代码如下:

类主

package restaurant;

import java.util.concurrent.Semaphore;

public class Main 

    public static int MAX_NUM_MEALS = 5;
    public static int OLDEST_MEAL = 0;
    public static int NEWEST_MEAL = -1;
    public static int DONE_MEALS = 0;

    public static int NUM_OF_COOKS = 1;
    public static int NUM_OF_WAITERS = 5;

    public static Semaphore mutex = new Semaphore(1);

    static Cook cookThreads[] = new Cook[NUM_OF_COOKS];
    static Waiter waiterThreads[] = new Waiter[NUM_OF_WAITERS];

    public static void main(String[] args) 
        for(int i = 0; i < NUM_OF_COOKS; i++) 
            cookThreads[i] = new Cook(i);
            cookThreads[i].start();
        

        for(int i = 0; i < NUM_OF_WAITERS; i++) 
            waiterThreads[i] = new Waiter(i);
            waiterThreads[i].start();
        
        try 

            for(int i = 0; i < NUM_OF_COOKS; i++) 
                cookThreads[i].join();
            

            for(int i = 0; i < NUM_OF_WAITERS; i++) 
                waiterThreads[i].join();
            

        catch(InterruptedException e) 
            e.printStackTrace();
        

        System.out.println("All done");

    



类厨师

package restaurant;

public class Cook extends Thread

    private int id;

    public Cook(int id) 
        this.id = id;
    

    public void run() 
        while(true) 
            System.out.println("Cook " + id + " is prepearing meal");
            try 
                Thread.sleep(1000);

                Main.mutex.acquire();
                Main.NEWEST_MEAL++;
                Main.mutex.release();

                Main.mutex.acquire();
                Main.DONE_MEALS++;
                Main.mutex.release();

                System.out.println("Cook " + id + " has finished the meal");

                if(Main.DONE_MEALS == 5) 
                    System.out.println("Cook " + id + " has finished his job");
                    break;
                

             catch (InterruptedException e) 
                // TODO Auto-generated catch block
                e.printStackTrace();
            
        
    

班级服务员

package restaurant;

public class Waiter extends Thread
    private int id;

    public Waiter(int id) 
        this.id = id;
    

    public void run() 
        while(true) 
            System.out.println("Waiter " + id + " will check if there is any meal to serve");
            if(Main.NEWEST_MEAL >= Main.OLDEST_MEAL) 
                try 
                    Main.mutex.acquire();
                    Main.OLDEST_MEAL++;
                    Main.mutex.release();

                    System.out.println("Waiter " + id + " is picking up meal");

                    Thread.sleep(500);

                    System.out.println("Waiter " + id + " has delivered the meal to client");                   

                 catch (InterruptedException e) 
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                
            
            if(Main.DONE_MEALS == 5) 
                System.out.println("Waiter " + id + " has finished his job");
                break;
            
            System.out.println("No meal to serve. Waiter " + id + " will come back later");

            try 
                Thread.sleep(100);
             catch (InterruptedException e) 
                // TODO Auto-generated catch block
                e.printStackTrace();
            
        
    

【问题讨论】:

考虑如果 6 个或更多厨师增加了DONE_MEALS 会发生什么,以及这如何影响您的停止条件if(Main.DONE_MEALS == 5)... 【参考方案1】:

两个问题:

    因为您有两位厨师,您的一位厨师可能不会看到Main.DONE_MEALS == 5。由于另一个厨师,它会从 4 跳到 6。相反,请检查 Main.DONE_MEALS &gt;= 5。 无法保证厨师或服务员线程会看到Main.DONE_MEALS 的更新。相反,请考虑使用 private static final AtomicInteger 字段。 AtomicInteger 类是线程安全的整数实现,它使其他线程能够以线程安全的方式看到它。

【讨论】:

【参考方案2】:

传统的解决方法是:

a) 不仅在写入时,而且在读取时都必须使用锁(互斥锁)——否则它将无法正常工作。 想象一下,您同意一个信号来指示浴室是否忙碌,但有些人只是决定忽略它 - 行不通!

b) 在做某事之前检查条件。 一旦你获得了锁,你就不知道状态,所以你应该先检查它,然后再继续做另一顿饭。如果您首先检查是否已经有 5 个完成的饭菜,如果还没有 5 个则只生产饭菜,它应该可以解决这个问题,并且您应该只会看到 done_meals &lt;= 5(您应该查看代码的其他部分,因为它有类似的问题,但)。

就像其他人提到的那样,有更简洁的方法来编写这个,但 IMO 你的代码非常适合练习和理解,所以我会尝试而不是跳到像 AtomicInteger 这样的东西。

【讨论】:

以上是关于Java线程项目奇怪的行为,同时增加线程数的主要内容,如果未能解决你的问题,请参考以下文章

Java 7 G1GC 奇怪的行为

Java多线程--两个线程同时对一个人的年龄进行增加和修改

Java程序员必备!Java底层分析多线程行为附答案

Cassandra java驱动程序 - 使用多线程提取数据时的高延迟

推断为什么线程在java应用程序中增加了

java.nio.ByteBuffer.slice() 线程行为?