【问题标题】:Multithreading deadlocks in JavaJava中的多线程死锁
【发布时间】:2014-03-20 12:59:25
【问题描述】:

我正在用 Java 为游戏编写一个机器人。一个线程管理 Manager 和 Worker 线程的时间并为它们发送心跳。 Manager 收集并解释来自服务器的消息,Worker 线程从 Manager 接收命令(在类中带有详细信息)并根据这些信息进行操作。

现在,我遇到了多线程死锁问题,我不确定是否有替代方案。

以下是我的管理器类中的一些方法: 当用户单击按钮时,我从 GUI 线程调用 getMessageFromUsername。从另一个线程调用以下方法。

private ArrayList<Message> MessageList = new ArrayList<>();

public synchronized Message getMessageFromUsername(String username) {        
        for( Message msg : MessageList ) {
            if( msg.username.equalsIgnoreCase(username) ) {
                Message m = new Message(msg.num, msg.username, msg.id);                
                return m;
            }
        }

        return null;
    }

我的管理器线程从套接字读取信息,并在一个连续循环中将信息添加到 MessageList。 (另一个,即)

private synchronized void parseMessage() {}
private void main() { while(1) { parseMessage(/*Adds message to MessageList*/); Sleep(); } }

现在,我的问题是一个非常菜鸟的问题——因为如果我想写入 MessageList,我必须使用同步来访问它。但是,由于这发生在循环中并且消息不断通过套接字进入,因此会导致死锁,因为它会一直锁定我的对象,并且这会在循环中发生。

我可以做些什么来解决我的死锁问题?

【问题讨论】:

  • 最简单的方法是在循环中添加一些暂停。你可以使用Thread.sleep()
  • 我在循环中使用了 100 毫秒的暂停,但由于某种原因它仍然死锁。什么是暂停的好时机?
  • 错错错! @bali182 请说你在拖钓他!到底睡觉怎么会是解决僵局的正确方法?另一方面,您究竟在哪里看到了死锁?你这里有一个饥饿线程的例子。 synchronized 原语不是你的解决方案,你应该使用一些优先机制。
  • @bali182 这是一个糟糕的建议。这就是为什么不应该将答案发布为 cmets 的原因,因为 cmets 不能被否决。
  • @Jason 你真的是指 deadlock 还是你真的指的是 starvation?此外,请查看 java.util.concurrent.* 类,了解更精细的多线程集合方法。

标签: java multithreading sockets deadlock synchronized


【解决方案1】:

我认为您应该使用 ArrayBlockingQueue 并避免使用循环,而是使用 poll/take/put/offer 等阻塞方法替换循环。

基于数组的集合表现更好,因此您应该首先考虑这些,然后是基于链接节点的集合。所以 ArrayBlockingQueue 比 ConcurrentLinkedQueue 要好。

【讨论】:

    【解决方案2】:

    正如我在评论中所说,你没有死锁,但你的线程饿死了。

    你有两个选择:

    • 要么选择一些具有优先顺序的线程

    • 或者您选择启用并发的集合。 java.util.concurrent 有很多此类集合的示例。但请注意 - 修改一个 foreached 的集合绝不是一个好主意。也许你只需要一种先进先出队列?一个线程添加消息,另一个线程弹出并评估?

    例如有ConcurrentLinkedQueue,它提供Add 方法(供您的parseMessage 使用)和isEmpty()(用于您其他线程的while 循环)。

    虽然我不是 Java 专家,但我会告诉你这个答案:How to use ConcurrentLinkedQueue? 它提供了一些实用的建议。

    【讨论】:

    • 谢谢。我现在正在研究线程饥饿。看来我错了,这是一个僵局。
    • 为线程分配优先级时的一般经验法则:释放资源的线程优先于消耗它的线程。将消息添加到队列的线程正在消耗资源(内存)。从队列中删除消息并对它们执行某些操作的线程(可能)在执行过程中释放内存。
    • @jameslarge 非常真实!但请始终记住,当消费者完成时,允许其他线程完成它的工作;-)
    • 我看不出优先级锁在这里有什么帮助,但是使用启用并发的集合可能会避免这个问题。不过,我认为准确识别问题仍然有意义。
    • @StefanHaustein 也许“锁”这个词在这种情况下并不是最好的。但总的来说,我的意思和 james large 一样
    【解决方案3】:

    如果我正确理解了您的用例,那么我相信我会考虑为您的消息列表使用 BlockingQueue 的实现。正如已经提到的,还提供了并发实现,因此您不需要外部同步。您应该可以从队列中简单地poll()take()

    【讨论】:

      【解决方案4】:

      我认为主要问题是您似乎在等待消息到达时一直保持锁定。

      当您开始阅读消息时,您通过parseMessage() 锁定- 可能有一段时间不会到达,并且其他线程在消息实际到达之前没有机会做任何事情。然后它可能会错过它的机会......(等待/通知可能会解决问题的后半部分,但我认为这里不需要它)。

      我将重组如下:

       // just parse and return the message, don't add it to the list
       private Message parseMessage() {} 
      
       private void main() {
          while(1) {
             Message msg = parseMessage();
             synchronized(messageList) {
               messageList.add(msg);
             }
          } 
       }
      

      【讨论】:

      • 在我看来,虽然尽可能少地同步是非常明智的,但在任何 IO 上同步都是一个坏主意,但值得一提的是,这并不能解决饥饿的问题。就像睡眠一样,它只是移动压力并(极大地)增加线程交织的机会,但从理论的角度来看并没有提供任何保证。不过在这种情况下它可能会有很大帮助;-)
      • 那么您在哪里看到更改后的程序会出现饥饿的可能性?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多