【问题标题】:Execution of threads in a queue in JavaJava中队列中线程的执行
【发布时间】:2015-06-21 13:45:45
【问题描述】:

我有这个练习:

开发多线程应用程序。 使用 java.util.concurrent 机会。

不要使用:同步、BlockingQueue、BlockingDeque

希望访问资源的所有实体都必须是线程。使用 OOP 的机会。

而我的任务是:

免费收银台。快餐店有几个收银台。客户在特定收银台排队,但可能会去 如果那里的队列减少或消失,则到另一个收银台。

这是我的解决方案https://github.com/NikitaMitroshin/FreeCash

public class Restaurant {
    private static Restaurant instance = null;
    private static ReentrantLock lock = new ReentrantLock();
    private String name;
    private ArrayList<CashDesk> cashDesks;

    private Restaurant(String name) {
        this.name = name;
        cashDesks = new ArrayList<>();
    }

    public static Restaurant getInstance(String name) {
        lock.lock();
        try {
            if (instance == null) {
                instance = new Restaurant(name);
            }
        } finally {
            lock.unlock();
        }
        return instance;
    }

    public void addCashDesk(CashDesk cashDesk) {
        cashDesks.add(cashDesk);
    }

    public String getName() {
        return name;
    }

    public List<CashDesk> getCashDesks() {
        return Collections.unmodifiableList(cashDesks);
    }
}

客户端代码:

public class Client extends Thread {
    private final static Logger LOG = Logger.getLogger(Client.class);
    private Restaurant restaurant;
    private CashDesk cashDesk;
    private String name;
    private int itemsInOrder;

    public Client(Restaurant restaurant, int itemsInOrder, String name) {
        this.restaurant = restaurant;
        this.itemsInOrder = itemsInOrder;
        this.name = name;
    }

    public String getClientName() {
        return name;
    }

    public int getItemsInOrder() {
        return itemsInOrder;
    }

    @Override
    public void run() {
        System.out.println("Client " + name + " comes to restaurant " + restaurant.getName());
        this.cashDesk = chooseCashDesk();
        System.out.println("Client " + getClientName() + " choosed the cashDesk#"+ cashDesk.getNumber());
        cashDesk.addClient(this);
        while (true) {
            if (cashDesk.getLock().tryLock()) {
                try {
                    cashDesk.serveClient(this);
                } catch (ResourceException e) {
                    LOG.error("ResourceException!!! ", e);
                } finally {
                    cashDesk.getLock().unlock();
                    break;
                }
            } else {
                if (canChooseAnotherCashDesk()) {
                    cashDesk.removeClient(this);
                }
            }
        }
        cashDesk.removeClient(this);
        System.out.println("Client " + getClientName() + " leaves restaurant");
    }

    private CashDesk chooseCashDesk(){
        CashDesk result = restaurant.getCashDesks().get(0);
        for (CashDesk cashDesk : restaurant.getCashDesks()) {
            if(cashDesk.getClients().size() < result.getClients().size()) {
                result = cashDesk;
            }
        }
        return result;
    }

    private boolean canChooseAnotherCashDesk() {
        CashDesk result = chooseCashDesk();
        if(result.getClients().size() + 1 < cashDesk.getClients().size()) {
            cashDesk = result;
            cashDesk.addClient(this);
            System.out.println("Client " + getClientName() + " moved to cashDesk#" + cashDesk.getNumber());
            return true;
        }
        return false;
    }
}

CashDesk 代码:

public class CashDesk {

    private ReentrantLock lock = new ReentrantLock();
    private LinkedList<Client> clients;
    private int number;
    private int timeOfService;

    public CashDesk(int number, int timeOfService) {
        clients = new LinkedList<>();
        this.number = number;
        this.timeOfService = timeOfService;

    }

    public void serveClient(Client client) throws ResourceException {
        System.out.println("Client "+client.getClientName() + " is serving on cashDesk#"+getNumber());
        try {
            client.sleep(timeOfService * client.getItemsInOrder());
        } catch (InterruptedException e) {
            throw new ResourceException("InterruptedException!!!", e);
        }
        System.out.println("Client "+client.getClientName() + " is served");
    }

    public List<Client> getClients() {
        return Collections.unmodifiableList(clients);
    }

    public void addClient(Client client) {
        clients.add(client);
    }

    public void removeClient(Client client) {
        clients.remove(client);
    }

    public int getNumber() {
        return number;
    }

    public ReentrantLock getLock() {
        return lock;
    }
}

跑步者代码:

public class RestaurantRunner {

    public static void main(String[] args) {
        Restaurant restaurant = Restaurant.getInstance("Mcdonalds");
        CashDesk cashDesk1 = new CashDesk(1, 140);
        CashDesk cashDesk2 = new CashDesk(2, 250);

        restaurant.addCashDesk(cashDesk1);
        restaurant.addCashDesk(cashDesk2);


        new Client(restaurant, 100, "client50").start();
        Random random = new Random();
        for (int i = 1; i < 8; i++) {

            int randNumbOfItems = random.nextInt(10) + 1;
            Client client =  new Client(restaurant, randNumbOfItems, "client"+i);
            client.start();
        }
    }
}

我有问题。这就是我运行应用程序后得到的结果

Client client1 comes to restaurant Mcdonalds
Client client1 choosed the cashDesk#1
Client client1 is serving on cashDesk#1
Client client3 comes to restaurant Mcdonalds
Client client3 choosed the cashDesk#2
Client client3 is serving on cashDesk#2
Client client5 comes to restaurant Mcdonalds
Client client5 choosed the cashDesk#1
Client client6 comes to restaurant Mcdonalds
Client client6 choosed the cashDesk#2
Client client4 comes to restaurant Mcdonalds
Client client4 choosed the cashDesk#1
Client client50 comes to restaurant Mcdonalds
Client client50 choosed the cashDesk#2
Client client7 comes to restaurant Mcdonalds
Client client7 choosed the cashDesk#1
Client client2 comes to restaurant Mcdonalds
Client client2 choosed the cashDesk#2
Client client1 is served
Client client5 is serving on cashDesk#1
Client client1 leaves restaurant
Client client3 is served
Client client3 leaves restaurant
Client client50 is serving on cashDesk#2
Client client5 is served
Client client5 leaves restaurant
Client client7 is serving on cashDesk#1
Client client7 is served
Client client7 leaves restaurant
Client client6 moved to cashDesk#1
Client client6 is serving on cashDesk#1
Client client2 moved to cashDesk#1
Client client6 is served
Client client6 leaves restaurant
Client client2 is serving on cashDesk#1
Client client2 is served
Client client2 leaves restaurant
Client client4 is serving on cashDesk#1
Client client4 is served
Client client4 leaves restaurant
Client client50 is served
Client client50 leaves restaurant

所以,如您所见,服务队列受到干扰。

当客户端 3 被服务时,客户端 6 必须开始服务,但客户端 50 这样做。当客户端 5 服务时,客户端 4 必须开始服务,但客户端 7 这样做。当 client7 服务时,我不知道为什么,但是 client6 移动到 cashDesk#1 并开始服务,尽管 client4 必须开始服务。

我是多线程新手,所以我需要一个建议,如何让我的应用程序正常工作

【问题讨论】:

  • 您应该在问题中包含您的代码 - 至少是相关部分。指向 GIThub 的链接不是一个好主意,因为很少有人会费心去那里浏览。此外,您的问题似乎至少部分是机器翻译的,这使得它难以理解。我看到您在俄罗斯 SO 上也很活跃。在那里发帖不是更好吗?
  • @RealSkeptic 什么是难以理解的?我可以试着解释一下
  • 我想你可以在CashDesk.clients列表上得到一个ConcurrentModificationException:多个线程(Clients)可以同时改变这个列表。使用Collections.synchronizedList 同步列表可能是个好主意。

标签: java multithreading concurrency reentrantlock


【解决方案1】:

您在标题中谈到了队列,但您没有在代码中使用它们。事实上,当第一个客户 (client5) 来到 cashdesk1 时,cashdesk 被锁定以服务该客户。

  //client code
          while (true) {
        if (cashDesk.getLock().tryLock()) {  //the cashdesk is locked
            try {
                cashDesk.serveClient(this);

与此同时,由于有服务时间,其他客户来了。所以client4和client7在cashdesk1等待
当client5被服务时,client5释放锁

  //client code
   cashDesk.getLock().unlock();

所以下一个服务是第一个获取锁的,因为它是一个无限循环,你无法知道每个客户端在代码中的哪个位置。所以client7在client4之前先抓取它。此外,在读取您的输出时,client2 之前也会抓取它。
建议你把锁去掉,用变量来指定顺序

 //CashDesk
  Client current=null;
  public void nextClient() 
  {
    if(clients.size()==0)
        current=null;
    else
        current = clients.get(0);
 } 

替换代码后面的部分

 while (true) {
        if (cashDesk.getLock().tryLock()) {
            try {
                cashDesk.serveClient(this);
            } catch (ResourceException e) {
                LOG.error("ResourceException!!! ", e);
            } finally {
                cashDesk.getLock().unlock();
                break;
            }
        } else {
            if (canChooseAnotherCashDesk()) {
                cashDesk.removeClient(this);
            }
        }
    }

通过

  while (true) {
        if(cashDesk.current==null)
             cashDesk.nextClient();
        if (current==this) {
            try {
                cashDesk.serveClient(this);
            } catch (ResourceException e) {
                LOG.error("ResourceException!!! ", e);
            } finally {
                cashDesk.nextClient();
                break;
            }
        } else {
            if (canChooseAnotherCashDesk()) {
                cashDesk.removeClient(this);
            }
        }
    }

【讨论】:

    【解决方案2】:

    所以,如您所见,服务队列受到干扰。

    它没有被打扰。它实际上按照您的设计工作。

    ReentrantLock 文档:

    这个类的构造函数接受一个可选的公平参数。 当设置为 true 时,在争用情况下,锁倾向于授予对 等待时间最长的线程。 否则此锁不保证任何 特定的访问顺序。 使用公平锁的程序被许多人访问 线程可能会显示较低的整体吞吐量(即较慢;通常 慢得多)比那些使用默认设置,但有更小的 获得锁和保证不饿死的时间差异。

    但是请注意,锁的公平性并不能保证锁的公平性 线程调度。因此,使用公平锁的许多线程之一可能 连续多次获取,同时其他活动线程 没有进展并且当前没有持有锁。

    另请注意, untimed tryLock 方法不遵守公平设置。它会 即使其他线程正在等待,如果锁可用,则成功。

    尝试熟悉ReentrantLock 的用法。 fairness 参数(您可以将其作为值传递给constructor)是查看您的客户是否按照您希望的顺序获得服务的第一步。 两种解决方案(另一个答案建议使用变量来控制顺序,尽管我不是这个的忠实粉丝 - 或者我对公平参数集的建议)并给我们提供更多反馈。

    【讨论】:

      猜你喜欢
      • 2013-07-31
      • 1970-01-01
      • 2011-06-09
      • 2012-07-30
      • 2012-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多