java 更新/写入静态变量的最佳实践?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4457042/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best practice for updating/writing to static variable?
提问by Charles
I have a project which displays department documentation. I store all the docs (get from database) in a static arrayList. Every X hour, I have that arrayList rebuilt based on the new doc (if any) from the database. There is also a static variable to control to rebuild that array or not, set and unset in a method which does the rebuild task. Each web browser hit the server will create this class's instance, but the doc arrayList and that control variable is shared among all the class instances.
我有一个显示部门文档的项目。我将所有文档(从数据库中获取)存储在一个静态 arrayList 中。每 X 小时,我都会根据数据库中的新文档(如果有)重建该 arrayList。还有一个静态变量来控制是否重建该数组,在执行重建任务的方法中设置和取消设置。每个访问服务器的 Web 浏览器都将创建此类的实例,但 doc arrayList 和该控制变量在所有类实例之间共享。
Find-Bugs tool complains that "Write to static field someArrayName and someVariableName from instance method someClassMethod". Seems this is not the good thing to do (let class instance method write to static field). Does anyone have good recommendation how to get around this problem ? Thanks.
Find-Bugs 工具抱怨“从实例方法 someClassMethod 写入静态字段 someArrayName 和 someVariableName”。似乎这不是一件好事(让类实例方法写入静态字段)。有没有人有很好的建议如何解决这个问题?谢谢。
回答by Jason S
Per the FindBugs bug descriptions:
ST: Write to static field from instance method (ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD)
This instance method writes to a static field. This is tricky to get correct if multiple instances are being manipulated, and generally bad practice.
ST:从实例方法写入静态字段 (ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD)
此实例方法写入静态字段。如果操作多个实例,这很难纠正,而且通常是不好的做法。
Aside from the concurrency issues, it means that all of the instances in the JVM are accessing the same data, and would not allow two separate groups of instances. It would be better if you had a singleton "manager" object and passed it to each of the instances as a constructor parameter or at least as a setManager()
method argument.
除了并发问题之外,这意味着 JVM 中的所有实例都在访问相同的数据,并且不允许两个单独的实例组。如果您有一个单例“管理器”对象并将其作为构造函数参数或至少作为setManager()
方法参数传递给每个实例会更好。
As for the concurrency issues: if you must use a static field, your static field should be final; explicit synchronization is difficult. (There are also some tricky aspects if you are initializing non-final static fields, beyond my knowledge of Java but I think I've seen them in the Java Puzzlers book.) There are at least three ways of dealing with this (warning, untested code follows, check first before using):
至于并发问题:如果你必须使用静态字段,你的静态字段应该是最终的;显式同步很困难。(如果您正在初始化非最终静态字段,也有一些棘手的方面,超出了我对 Java 的了解,但我想我已经在 Java Puzzlers 书中看到了它们。)至少有三种方法可以解决这个问题(警告,未经测试的代码如下,使用前先检查):
Use a thread-safe collection, e.g.
Collections.synchronizedList
wrapped around a list that is not accessed in any other way.static final List<Item> items = createThreadSafeCollection(); static List<Item> createThreadSafeCollection() { return Collections.synchronizedList(new ArrayList()); }
and then later when you are replacing this collection, from an instance:
List<Item> newItems = getNewListFromSomewhere(); items.clear(); items.add(newItems);
The problem with this is that if two instances are doing this sequence at the same time, you could get:
Instance1: items.clear(); Instance2: items.clear(); Instance1: items.addAll(newItems); Instance2: items.addAll(newItems);
and get a list that doesn't meet the desired class invariant, namely that you have two groups of newItems in the static list. So this method doesn't work if you are clearing the entire list as one step, and adding items as a second step. (If your instances just need to add an item, though,
items.add(newItem)
would be safe to use from each instance.)Synchronize access to the collection.
You'll need an explicit mechanism for synchronizing here. Synchronized methods won't work because they synchronize on "this", which is not common between the instances. You could use:
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; } }
use AtomicReference
static final private AtomicReference<List<Item>> list; static void setList(List<Item> newList) { list.set(newList); }
使用线程安全的集合,例如
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);
并获得一个不符合所需类不变量的列表,即静态列表中有两组 newItem。因此,如果您将整个列表作为一个步骤清除,并将项目添加作为第二个步骤,则此方法不起作用。(不过,如果您的实例只需要添加一个项目,则
items.add(newItem)
可以安全地从每个实例使用。)同步对集合的访问。
您将需要一个显式的同步机制。同步方法将不起作用,因为它们在“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; } }
使用原子引用
static final private AtomicReference<List<Item>> list; static void setList(List<Item> newList) { list.set(newList); }
回答by Tim Bender
If I understand the message you posted from Find Bugs correctly, this is just a warning.
如果我正确理解了您从 Find Bugs 发布的消息,这只是一个警告。
If you want to hide the warning, do the modifications from a static method. Find Bugs is warning you because this is typically an error. The programmer thinks they are changing some instance state but really they are changing some state which impacts every instance.
如果要隐藏警告,请从静态方法进行修改。Find Bugs 会警告您,因为这通常是一个错误。程序员认为他们正在改变一些实例状态,但实际上他们正在改变一些影响每个实例的状态。
回答by drekka
You don't need to delete the list each time. As per above you will have to deal with multiple threads, but you can create the ArrayList once, then use clear() and addAll() methods to wipe and repopulate. FindBugs should be quite happy with that because you are not setting a static.
您不需要每次都删除列表。如上所述,您将不得不处理多个线程,但您可以创建一次 ArrayList,然后使用 clear() 和 addAll() 方法来擦除和重新填充。FindBugs 应该对此感到非常满意,因为您没有设置静态。
guys - feel free to chip in if there is any problem with this technique :-)
伙计们 - 如果这项技术有任何问题,请随时参与:-)
A second thought is to drive things from the database via hibernate. So don't maintain a list, hibernate has inbuilt caching so it's almost as quick. If you update the data at the database level (which means hibernate doesn't know) you can then tell hibernate to clear it's cache and refresh from the database when it's next queried.
第二个想法是通过休眠从数据库中驱动事物。所以不要维护一个列表,hibernate 有内置的缓存,所以它几乎一样快。如果您在数据库级别更新数据(这意味着 hibernate 不知道),那么您可以告诉 hibernate 清除它的缓存并在下次查询时从数据库中刷新。
回答by user541686
Using the Singletondesign pattern is one way. You can have only one instance of an object that holds the value you want, and access that instance through a global property. The advantage is that, if you want to have more instances later on, there's less modification of preexisting code (since you're not changing static fields to instance fields).
使用单例设计模式是一种方法。您只能拥有一个对象实例来保存您想要的值,并通过全局属性访问该实例。优点是,如果您以后想拥有更多实例,则对预先存在的代码的修改较少(因为您没有将静态字段更改为实例字段)。
回答by hvgotcodes
You do not want to do this. Every request runs in its own thread. If the code that gets executed on a browser action modifies the list, then two requests can possibly modify the list at the same time, and corrupt the data. That is why it is not a good idea to access static resources from a non-static context, and probably why your tool is warning you.
你不想这样做。每个请求都在自己的线程中运行。如果在浏览器操作上执行的代码修改了列表,那么两个请求可能会同时修改列表,并破坏数据。这就是为什么从非静态上下文访问静态资源不是一个好主意的原因,这可能也是您的工具警告您的原因。
Look at this
看这个
http://download.oracle.com/javase/6/docs/api/index.html?java/util/concurrent/package-summary.html
http://download.oracle.com/javase/6/docs/api/index.html?java/util/concurrent/package-summary.html
specifically the part about how the ArrayList is not synchronized. Also note that the paragraph I mention has a solution, specifically
特别是关于 ArrayList 如何不同步的部分。另请注意,我提到的段落有一个解决方案,特别是
List list = Collections.synchronizedList(new ArrayList(...));
Thats one way to do it. But its still not a good idea, namely because it can be slow. If its not a commercial-grade application, and you are not dealing in high volume, you can probably get by not making it better. If this is the type of app that only gets hit a few times per day, you can ignore the warning, with the understanding that its is possiblethat something bad will happen if two requests munge each other.
那是一种方法。但这仍然不是一个好主意,即因为它可能很慢。如果它不是商业级应用程序,并且您的交易量不是很大,那么您可能可以通过不改进它来获得。如果这是每天只被点击几次的应用程序类型,您可以忽略该警告,并了解如果两个请求相互冲突,可能会发生一些不好的事情。
A better solution: Since you have database, I would just get the information from the db as you need it, i.e. as the requests come in. You can use some caching technologies for performance.
一个更好的解决方案:因为你有数据库,我只会在你需要的时候从数据库中获取信息,即当请求进来时。你可以使用一些缓存技术来提高性能。
The reason I don't like the Singleton Pattern idea is that even if it makes the warning go away, it doesn't address the fundamental synchronization problem, by itself. There are thread safe http://en.wikipedia.org/wiki/Singleton_pattern#Traditional_simple_way_using_synchronization, however, which might work in this case.
我不喜欢单例模式想法的原因是,即使它使警告消失,它本身也不能解决基本的同步问题。但是,有线程安全的http://en.wikipedia.org/wiki/Singleton_pattern#Traditional_simple_way_using_synchronization,这可能在这种情况下有效。