【问题标题】:How to make the static method thread safe in JAVA?如何在 JAVA 中使静态方法线程安全?
【发布时间】:2015-02-14 07:20:14
【问题描述】:

我正在创建一个 Web 应用程序并遇到线程安全问题。在阅读了几个类似的问题之后,我仍然对我的情况感到困惑。我正在使用 java spring 框架来设置 REST Web 服务。所有请求(Person 对象的 JSON)都将传递给 checkIfGoodName 函数,例如 Checker.checkIfGoodName(person)。它们都是静态方法调用。我想知道,这个功能是Checker.checkIfGoodName THREAD SAFE 吗?如果没有,如何修改代码?我的代码如下:

Checker.java

public class Checker {

    public static void checkIfGoodName(Person person){

        checkingName(Person person);

    }

    private static void checkingName(Person person){

        if(person.getName()==null){
            PersonUtils.addErrorMessage(Person person, new String("Name is empty"));
        }

    }

}

PersonUtils.java

public class PersonUtils {

     public static void addErrorMessage(Person person, String errorMessage){

         List<Message> msg = person.getMessageList();
         if(msg!=null){
             msg.add(buildMessage(errorMessage));
         }
     }

     public static void buildMessage(String errorMessage){
         if(errorMessage != null){
             Message msg = new Message();
             msg.setMsg(errorMessage);
         } 
     }

}

【问题讨论】:

  • buildMessage 需要返回一些东西
  • 如果你像msg.add(buildMessage())这样称呼它,它不可能是void

标签: java multithreading thread-safety static-methods


【解决方案1】:

我没有看到PersonUtils 有状态,但是可能是您有相同的Person 实例同时传递两次,因此最好锁定Person 实例,

假设您实际上有与实例关联的消息列表,而不是 static 属性

将错误消息与Model本身关联也不好,尝试使用您提到的框架探索一些标准实践

【讨论】:

  • 如果Person 需要锁定,那么最好将关键代码移动到Person 本身(看到它已经有消息列表)。或者,按照您的建议,将邮件列表从 Person 中完全删除。
  • Person 中删除messageList 完全胜出
  • "remove messageList":您可以有一个“PersonValidator”,它将Person 与消息列表(和相关逻辑)结合起来,使看起来像 UI 状态的内容与模型类分开。这些对象中的任何一个都可能不需要在多个线程之间共享。
  • 感谢你们的回复。我的 web 服务将 person json 转换为 person 类,作为 checkIfGoodName 的输入,并且调用了几个静态方法并更新了 person 消息成员变量。其他请求将根据他们的 json 输入创建不同的人员对象并传递给 checkIfGoodName。因此,我可以说,没有多个线程同时访问共享对象,因此我的代码没有线程安全问题吗?
  • 我仍然建议将消息​​与 Person 分开
【解决方案2】:

不要考虑让方法线程安全。线程安全是关于保护数据的完整性。更具体地说,它是关于在其他线程正在更改数据的过程中阻止线程访问数据。

您的PersonUtils.addErrorMessage(person, message) 方法修改了属于Person 实例的List 实例。对列表的访问应该是synchronized 如果同一个列表可以被两个不同的线程修改,或者如果它可以被一个线程修改并被其他线程访问。

将一个项目添加到列表需要几个步骤,如果线程 A 在线程 B 执行了一些但不是所有步骤时能够看到它,那么列表几乎肯定会处于非法状态.如果两个线程同时尝试修改列表,情况会更糟:这可能会使列表处于永久的非法状态。

您将仍然需要同步,即使在同一 Person 实例上操作的线程实际上并没有同时进行同步。原因是,如果没有同步,计算机硬件和操作系统不能保证一个线程在内存中所做的更改将立即对其他线程可见。但是,synchronized 来救你了:

线程 A 在离开 synchronized(foo) 块之前所做的任何更改在线程 B 进入 synchronized(foo) 块后对线程 B 可见。

对你来说最简单的事情,同样如果不同的线程访问同一个 Person 实例,是在你的 addErrorMessage(...) 方法中同步 Person 对象:

  public static void addErrorMessage(Person person, String errorMessage){
      synchronized(person) {
          List<Message> msg = person.getMessageList();
          if(msg!=null){
             msg.add(buildMessage(errorMessage));
          }
      }
  }

【讨论】:

  • 感谢您的回复。实际上 PersonUtils.addMessage(...) 将消息对象添加到人员对象的消息列表中。所以,它会改变 person 对象。但我仍然可以认为它是线程安全的,因为它只会改变当前的 person 对象,而其他线程会改变它的 person 对象。没有共享人员对象,因此它是线程安全的。正确的 ?谢谢
  • @Terry,哎呀!你说的对。我没有仔细阅读您的 PersonUtils.addMessage(...) 。我会改变我的答案。
  • 示例中所有变量都是本地变量,为什么要使用同步并降低性能?
  • @Mikhail 示例中所有显式的变量都是本地的,但是示例调用了方法; PersonUtils.addErrorMessage(...)、person.getMessageList()、msg.add(...)。你能肯定地说这些方法都只涉及局部变量吗?如果这是真的,它们有什么用?
  • 是的。你说的对 。我更仔细地看了看。在这种情况下,我将重写代码并尽量避免使用同步
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-16
  • 2010-11-08
  • 2018-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多