【问题标题】:Is this static method thread safe or is synchronization needed这个静态方法是线程安全的还是需要同步的
【发布时间】:2016-05-15 21:03:51
【问题描述】:

我有一个实用程序类,它有一个静态方法来修改输入数组列表的值。此静态方法由调用者调用。调用者用于处理 Web 服务请求。对于每个请求(每个线程),调用者都会创建一个新的 ArrayList 并调用静态方法。

public class Caller{

   public void callingMethod(){

     //Get Cloned criteria clones a preset search criteria that has place holders for values and returns a new ArrayList of the original criteria. Not included code for the clone
     ArrayList<Properties> clonedCriteria = getClonedCriteria();

     CriteriaUpdater.update(clonedCriteria , "key1", "old_value1", "key1_new_value");
     CriteriaUpdater.update(clonedCriteria , "key2", "old_value2", "key2_new_value");

    //do something after the this call with the modified criteria arraylist
   }

}

public class CriteriaUpdater
{
    //updates the criteria, in the form of array of property objects, by replacing the token with the new value passed in
    public static void update(ArrayList<Properties> criteria, String key, String token, String newValue)
    {
        for (Properties sc: criteria)
        {
            String oldValue = sc.getProperty(key);
            if ((oldValue != null) && (oldValue.equals(token))) 
                sc.setProperty(key, newValue);
        }
    }
}

这是克隆标准的方式:

public synchronized static ArrayList<Properties> cloneSearchCriteria(ArrayList<Properties> criteria) { 
  if (criteria == null) return null; 
  ArrayList<Properties> criteriaClone = new ArrayList<Properties>(); 
  for (Properties sc : criteria) { 
    Properties clone = new Properties(); 
    Enumeration propertyNames = sc.propertyNames(); 
    while (propertyNames.hasMoreElements()) { 
      String key = (String) propertyNames.nextElement(); 
      clone.put(key, (String) sc.get(key)); 
    } 
    criteriaClone.add(clone);
  } 
  return criteriaClone; 
}

鉴于上述定义,通过不同步静态方法,它是否仍然能够正确处理并发方法调用。我的理解是我必须同步此方法以进行并发但想确认。 我知道每个线程都有自己的堆栈,但是对于静态方法,它对所有线程都是通用的 - 所以在这种情况下,如果我们不同步它不会导致问题吗? 感谢建议和任何更正。

谢谢

【问题讨论】:

  • “预设搜索条件”是否曾被另一个线程更改过? “之后做某事”是否对线程之间共享的任何数据做任何事情?
  • getClonedCriteria 是返回同步副本还是仅返回参考副本?
  • 预设标准没有改变。公共同步静态 ArrayList cloneSearchCriteria(ArrayList criteria) { if (criteria == null) return null; ArrayList criteriaClone = new ArrayList(); for (Properties sc : 标准) { 属性克隆 = 新属性 ();枚举propertyNames = sc.propertyNames(); while (propertyNames.hasMoreElements()) { String key = (String) propertyNames.nextElement(); clone.put(key, (String) sc.get(key)); } 标准Clone.add(克隆); } 返回条件克隆; }
  • 对不起。无法格式化代码..但是 cloneSearchCriteria() (我已为这篇文章重命名为 getClonedCriteria() 是同步的
  • 您仍然没有显示原始标准是如何初始化的。 cloneSearchCriteria() 方法只要处理好原始条件就不需要同步了。

标签: java multithreading concurrency thread-safety


【解决方案1】:

您的竞争条件有问题。至少底层的Properties 数据结构永远不会被破坏,但它可能具有不正确的值。特别是,这个部分可以有任意数量的线程,这意味着最终值可以是来自任何线程的任何值。

        String oldValue = sc.getProperty(key);
        if ((oldValue != null) && (oldValue.equals(token))) 
            sc.setProperty(key, newValue);

我假设您的列表永远不会更改,但如果是,您必须拥有synchronized。您可以锁定课程,但锁定您正在更改的集合可能是更好的选择。

【讨论】:

  • 不确定我是否理解正确。公共同步静态 ArrayList cloneSearchCriteria(ArrayList criteria) { if (criteria == null) return null; ArrayList criteriaClone = new ArrayList(); for (Properties sc : 标准) { 属性克隆 = 新属性();枚举propertyNames = sc.propertyNames(); while (propertyNames.hasMoreElements()) { String key = (String) propertyNames.nextElement(); clone.put(key, (String) sc.get(key)); } 标准Clone.add(克隆); } 返回条件克隆; }
  • 继续:你是说在 update() 中,我必须有一个 synchronized(criteria){ ... }
  • 随后的编辑似乎显示了数据的深层副本。你的答案仍然适用吗?
  • @peter - 原始列表在 spring 应用程序上下文中定义,并在应用程序启动时通过 spring 应用程序上下文注入调用者。事实上,在调用者中,我正在使用 getter 获取原始搜索条件,并且克隆的目的是对副本而不是原始文件进行任何修改。鉴于此,我是否必须使 code public static void update(ArrayList criteria, String key, String token, String newValue) 同步?
【解决方案2】:

您是正确的,因为每个线程都有自己的堆栈,因此每个线程在调用update() 时都会有自己的局部变量和方法参数的副本。当它运行时,它将那些局部变量和方法参数保存到它的堆栈中。

但是,方法参数criteria 是对可变对象的引用,该对象将存储在Java 对象所在的堆上。如果线程可以在同一个 ArrayList 上调用 update(),或者 ArrayList 中包含的元素可能包含在多个 ArrayList 中,由不同线程传递给 update() 的不同调用,则可能会发生同步错误。

【讨论】:

  • 条件是一个预设数组列表的克隆,这个克隆对于调用方法是本地的。因此,鉴于此,传递给 update() 的标准是否会被视为本地而不是共享的,因为我将传递一个引用。
【解决方案3】:

这完全取决于您的getClonedCriteria() 方法。这就是访问共享状态的方法。

您正在创建标准的“深层副本”,以便每个克隆都独立于原始副本和彼此。

但还有一个更微妙的问题,即对原型标准执行的任何初始化都必须发生在任何读取标准以克隆它的线程之前。否则,克隆线程可能会读取数据结构的未初始化版本。

实现此目的的一种方法是在静态初始化程序中初始化原型标准并将其分配给类成员变量。另一种方法是初始化条件然后将其分配给volatile 变量。或者,您可以初始化并将原型(以任意顺序)分配给synchronized 块(或使用Lock)内的普通类或实例成员变量,然后从在同一个锁上同步的另一个块中读取变量.

【讨论】:

  • 原始搜索条件是在应用启动时spring注入调用者(在spring中配置为单例)。在 spring app 上下文文件中,这个原始的搜索条件是用键值对初始化的。调用者然后调用实用程序类克隆方法以在处理 Web 服务请求时返回原始搜索条件的克隆(因为我想确保原始条件不被更改)。不确定这是否与您的建议相似
  • @sbelvadi 所以,getClonedCriteria() 是一个类似于 return cloneSearchCriteria(original) 的实例方法?在那种情况下,original(或任何你命名的)是由 Spring 注入设置的?
  • original 没有声明为 final 或 volatile,而是在 spring 应用程序上下文中定义,并且在应用程序启动时通过 spring 应用程序上下文注入调用者的依赖项。事实上,在调用者中,我正在使用 getter 获取原始搜索条件,并且克隆的目的是对副本而不是原始文件进行任何修改。鉴于此,我是否必须使 public static void update(ArrayList criteria, String key, String token, String newValue) 同步?
  • 没有。只要 Spring 注入像宣传的那样工作,这些代码都不需要任何同步。所有请求处理线程都将看到一个正确初始化的标准对象,并制作一个用于该请求的独立副本。 Spring 正在为您处理线程可见性问题。您的任何方法(包括 cloneSearchCriteria())都不需要同步。
  • 感谢您的 cmets 和您对我的问题的耐心。每天学习新东西
猜你喜欢
  • 2016-07-16
  • 2011-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-17
  • 2011-07-07
  • 2013-08-28
  • 1970-01-01
相关资源
最近更新 更多