更新/写入静态变量的最佳实践?

时间:2010-12-16 02:49:20

标签: java static findbugs

我有一个显示部门文档的项目。我将所有文档(从数据库中获取)存储在静态arrayList中。每隔X小时,我就根据数据库中的新doc(如果有的话)重建了arrayList。还有一个静态变量来控制重建该数组,在执行重建任务的方法中设置和取消设置。每个访问服务器的Web浏览器都将创建此类的实例,但doc arrayList和该控件变量在所有类实例之间共享。

Find-Bugs工具抱怨“从实例方法someClassMethod写入静态字段someArrayName和someVariableName”。似乎这不是好事(让类实例方法写入静态字段)。有没有人有好的建议如何解决这个问题?感谢。

5 个答案:

答案 0 :(得分:7)

根据FindBugs bug descriptions

  

ST:从实例方法写入静态字段(ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD)

     

此实例方法写入静态字段。如果操作多个实例并且通常是不好的做法,这很难得到纠正。

除了并发问题,它意味着JVM中的所有实例都在访问相同的数据,并且不允许两个单独的实例组。如果您有一个单独的“manager”对象并将其作为构造函数参数或至少作为setManager()方法参数传递给每个实例,那会更好。

至于并发问题:如果你必须使用静态字段,你的静态字段应该是最终的;显式同步很难。 (如果你正在初始化非最终静态字段,还有一些棘手的方面,除了我对Java的了解,但我认为我已经在Java Puzzlers书中看到过它们。)至少有三种方法可以解决这个问题(警告,未经测试的代码如下,在使用之前先检查):

  1. 使用线程安全集合,例如Collections.synchronizedList包裹在未以任何其他方式访问的列表中。

    static final List<Item> items = createThreadSafeCollection();
    
    
    static List<Item> createThreadSafeCollection()
    {
       return Collections.synchronizedList(new ArrayList());
    }
    

    然后当你从一个实例替换这个集合时:

    List<Item> newItems = getNewListFromSomewhere();
    items.clear();
    items.add(newItems);
    

    问题在于,如果两个实例同时执行此序列,您可以获得:

    Instance1:items.clear(); Instance2:items.clear(); Instance1:items.addAll(newItems); Instance2:items.addAll(newItems);

    并获取一个不符合所需类不变量的列表,即在静态列表中有两组newItems。因此,如果要将整个列表清除为一步,并将第二步添加为项,则此方法不起作用。 (但是,如果您的实例只需要添加一个项目,items.add(newItem)可以安全地从每个实例中使用。)

  2. 同步对集合的访问权限。

    这里需要一个明确的同步机制。同步方法不起作用,因为它们在“this”上同步,这在实例之间不常见。你可以使用:

    static final private Object lock = new Object();
    static volatile private List<Item> list;
    // technically "list" doesn't need to be final if you
    // make sure you synchronize properly around unit operations.
    
    
    static void setList(List<Item> newList)
    {
      synchronized(lock)
      {
          list = newList;
      }
    }
    
  3. 使用AtomicReference

    static final private AtomicReference<List<Item>> list;
    
    
    static void setList(List<Item> newList)
    {
      list.set(newList);
    }
    

答案 1 :(得分:1)

如果我理解您从Find Bugs正确发布的消息,这只是一个警告。

如果要隐藏警告,请从静态方法执行修改。查找错误警告您,因为这通常是一个错误。程序员认为他们正在改变一些实例状态,但实际上他们正在改变一些影响每个实例的状态。

答案 2 :(得分:0)

使用Singleton设计模式是一种方法。您只能拥有一个包含所需值的对象实例,并通过全局属性访问该实例。优点是,如果您希望以后有更多实例,则对预先存在的代码的修改较少(因为您没有将静态字段更改为实例字段)。

答案 3 :(得分:0)

您不需要每次都删除列表。如上所述,您将不得不处理多个线程,但您可以创建一次ArrayList,然后使用clear()和addAll()方法擦除和重新填充。 FindBugs应该对此非常满意,因为你没有设置静态。

如果这项技术有任何问题,请随时填写: - )

第二个想法是通过hibernate从数据库中驱动东西。所以不要维护一个列表,hibernate有内置的缓存,所以它几乎一样快。如果你在数据库级别更新数据(这意味着hibernate不知道),你可以告诉hibernate清除它的缓存并在下次查询时从数据库中刷新。

答案 4 :(得分:-1)

你不想这样做。每个请求都在自己的线程中运行。如果在浏览器操作上执行的代码修改了列表,则两个请求可能同时修改列表,并破坏数据。这就是为什么从非静态上下文访问静态资源不是一个好主意,也可能是你的工具警告你的原因。

看看这个

http://download.oracle.com/javase/6/docs/api/index.html?java/util/concurrent/package-summary.html

具体是关于ArrayList如何不同步的部分。另请注意,我提到的段落有一个解决方案,特别是

List list = Collections.synchronizedList(new ArrayList(...));

这是一种方法。但它仍然不是一个好主意,因为它可能很慢。如果它不是商业级的应用程序,并且你没有大批量交易,你可能会因为没有做得更好而得到。如果这是每天只被点击几次的应用类型,您可以忽略该警告,并理解它是可能如果两个请求互相攻击,则会发生不良事件。< / p>

更好的解决方案:由于您拥有数据库,因此我会根据您的需要从数据库中获取信息,即在请求进入时。您可以使用一些缓存技术来提高性能。

我不喜欢Singleton Pattern的想法是,即使它使警告消失,它也无法解决基本的同步问题。但是,有线程安全的http://en.wikipedia.org/wiki/Singleton_pattern#Traditional_simple_way_using_synchronization,在这种情况下可能有用。