【问题标题】:How can I make this thread safe?我怎样才能使这个线程安全?
【发布时间】:2012-04-15 14:01:50
【问题描述】:

我有一个服务器,它从客户端接收各种 xml 消息(每个客户端一个线程),并根据消息类型将消息路由到不同的函数。例如。如果消息中的第一个元素包含字符串“login”,则表示这是一条登录消息,因此将消息路由到 login() 函数。

无论如何,我想发出这条消息,这样如果连接了多个客户端并且调度程序在消息路由中间切换线程,事情就不会变得混乱。所以这就是我路由消息的方式-

public void processMessagesFromClient(Client client)
{
    Document message;

    while (true)
    {
        try
        {
            message = client.inputStream.readObject();

            /*
             * Determine the message type
             */
            String messageType = getMessageType(message);

            // Route the message depending on its type
            switch (messageType)
            {
                case LOGIN:
                    userModel.handleLogin();
                ...
                ...
                ...
                etc...
             }
        } catch(Exception e) {}
   }

那么我怎样才能使这个线程安全呢?我想我需要在某个地方放置一个同步语句,但我不确定在哪里。我也一直在阅读这个主题,我发现这篇文章说在“这个”上使用同步存在问题 - https://stackoverflow.com/a/416198/1088617

这里的另一篇文章说单例不适合使用同步(我上面代码中的类是单例)-https://stackoverflow.com/a/416202/1088617

【问题讨论】:

  • 你在哪里开始新线程?
  • 所有客户端都使用上面的函数。我为每个客户端启动一个线程,在第一次检索输入/输出流之后,我使用上面的 processMessagesFromClient() 方法无限循环。因此,所有客户端都将共享“消息”变量。他们还将共享 userModel,因为这也是一个单例。当我收到来自客户端的消息时,我认为我应该阻止其他线程,直到我处理完该消息?

标签: java multithreading asynchronous thread-safety


【解决方案1】:

您的类已经线程安全,因为您只使用局部变量。 线程安全仅在您访问类 state(即字段)时发挥作用,而您的代码没有(似乎)这样做。

您所说的是序列化 - 您希望将所有消息处理集中到一个点,以保证消息处理一次一个(开始和结束原子)。解决方法很简单:使用static synchronized 方法:

public void processMessagesFromClient(Client client) {
    Document Message;

    while (true) {
        processMessage(client);
    }
}

private static synchronized processMessage(Client client) {
    try {
        message = client.inputStream.readObject();

        String messageType = getMessageType(message);

        // Route the message depending on its type
        switch (messageType) {
            case LOGIN:
                userModel.handleLogin();
            ...
            etc...
        }
    } catch(Exception e) {}
}

仅供参考static synchronized 方法使用 Class 对象作为锁。此代码将使您的代码表现得像一个单线程,这是您的问题似乎想要的。

【讨论】:

  • 干杯,我忘记了局部变量是线程安全的。
  • 在使用这段代码时要小心——你对“message = client.inputStream.readObject()”有什么期望?它可能正在阻塞呼叫 - 它会等到当前 cline 发送消息。在这种情况下,如果服务器正在等待某个特定客户端的消息,则所有客户端的处理都将停止。 Brian_CS:你能澄清一下“readObject()”是否等待消息吗?
  • 是的,它等待消息,inputStream 是一个 ObjectInputStream。
【解决方案2】:

我实际上会有一个消息处理线程,负责读取传入的消息。然后,这会将处理移交给工作线程以对消息进行耗时的处理。您可以使用 Java ThreadPoolExecutor 来管理它。

【讨论】:

    【解决方案3】:

    如果每个连接已经有 1 个线程,那么您唯一需要同步的是处理事件的函数(即像 userModel.handleLogin() 这样的函数)。

    【讨论】:

      【解决方案4】:

      我想最好的解决方案应该是使用像 ConcurrentQueue 这样的线程安全队列,并使用单个工作线程来获取这些值并逐个运行操作。

      【讨论】:

        【解决方案5】:

        只要每个线程都有这些对象之一,就没有问题。您只需要同步一个可以被其中一个线程修改的共享对象。

        public void processMessagesFromClient(Client client) {    
            while (true) {
                processMessage(client);
            }
        }
        
        private void processMessage(Client client) {
            try {
                Document message = client.inputStream.readObject();
        
                String messageType = getMessageType(message);
        
                // Route the message depending on its type
                switch (messageType) {
                    case LOGIN:
                        userModel.handleLogin();
                    ...
                    etc...
                }
            } catch(Exception e) {}
        }
        

        【讨论】:

        • 但是他说代码中的类是单例的。所以大概他有一些线程之间共享的数据结构。
        • 好吧,如果上面的代码正在执行并且为特定客户端(客户端 1)设置了“消息”变量......那么如果调度程序中断并让另一个客户端(客户端 2)使用线程run 'message' 变量不能被这个新客户端覆盖吗?当它切换回客户端 1 时,“消息”变量现在可以包含与客户端 2 相关的数据?
        • 您不能中断 ObjectInputStream。您已经按照您的建议为每个连接使用一个线程,这意味着您不能使用将共享线程用于多个连接的调度程序。你能澄清一下你在做什么吗?
        • dispatcher 并不是指一些与 Java 相关的 Dispatcher 对象,我的意思是操作系统或 JVM 可以中断一个客户端线程并在我的消息路由中间开始执行另一个客户端线程。如果客户端 1 的线程正在执行并且设置了“消息”变量,则客户端 1 线程可能会被中断,然后客户端 2 的线程将覆盖“消息”变量中的数据,因此当客户端 1 的线程再次开始执行时,它将包含无效数据...
        • 你的方法是同步的,所以只有一个线程可以进入它。我不会那样做,而是每个线程都有一个message,因为一开始就没有必要遇到这个问题。
        【解决方案6】:

        您需要知道在某个时间应该只使用哪个资源作为一个线程。

        在您的情况下,阅读下一条消息很可能需要保护。

         synchronize (lock) {
              message = client.inputStream.readObject();
         }
        

        但是,您的代码示例并没有真正显示需要防止并发访问的内容

        【讨论】:

        • 是的,我相信“消息”需要受到保护。并且 userModel 也需要受到保护,因为它将加载与当前客户端相关的数据。我只想阻止所有其他客户端线程执行,直到我处理完当前消息。那么将“try”语句中的所有内容包装在同步块中是否可以?
        • @stefan bachert :正如你所说,没有足够的信息......但是假设 inputStream 是网络流并且 readObject() 可能会阻塞,我会说打这个电话不是个好主意进入同步块。如果其中一个线程在“readObject()”上阻塞,则所有其他线程都无法读取任何已经可用的消息。您认为这可能是个问题吗?
        • 同步任何已经同步的东西可能会导致死锁。我明白你的意思。
        【解决方案7】:

        方法本身是线程安全的。 但是,请注意您的类是单例,您可能希望在 getInstance 中使用 double checked locking 以确保线程安全。 此外,您应该确保您的实例设置为 static

              class Foo {
                    private static volatile Foo instance = null;
                    public static Foo getInstance() {
                        if (instance == null) 
                        {
                            synchronized(this) 
                            {
                                if (instance == null)
                                    instance = new Foo ();
                            }
                        }
                        return instance ;
                    }
                }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-09
          • 2021-07-14
          相关资源
          最近更新 更多