"All concurrency issues boil down to coordinating access to mutable state. The less mutable state, the easier it is to ensure thread safety." -- Java Concurrency in Practice
So the question you must ask yourself are:
What is the inherent shared data that the my application will need?
When can a thread work on a snapshot of the data, that is, it momentary work on a clone of the shared data?
Can I identify known pattern and use higher-level abstraction rather than low-level locks and thread coordination, e.g. queues, executor, etc. ?
Think of a global locking scheme as to avoid deadlock and have a consistent acquisition of locks
The simplest approach to manage shared state is to serialize every action. This coarse-grained approach results however into a high lock contention and poor performance. Managing concurrency can be seen an optimization exercise where you try to reduce the contention. So subsequent questions are:
How would the simplest approach be?
What are the simple choice that I can make to reduce contention (possibly with fine grained locking) and improve performance without making the solution overly complicated?
When am I going too fined-grained, that is, the complexity introduced isn't worth the performance gain?
A lot of approach to reduce contention rely on some form of trade-off between what would be necessary to enforce the correct behavior and what is feasible to reduce contention.
Where can I relax a few constraint and accept that sometimes stuff won't be 100% correct (e.g. a counter) ?
Can I be optimistic and deal with conflict only when concurrent modifications happen (e.g. using time stamp and retry logic - that's what TM do)?
Note that I never worked on a game, only on server-side part of enterprise apps. I can imagine that it can be quite different.
我应该补充一点,在你未来的努力中需要记住的一些事情是 STM 和 Actor 模型。这两种并发方法都显示出非常好的进展。虽然每个都有一些开销,但这取决于程序的性质,这可能不是问题。
编辑:
以下是一些您可以在下一个项目中使用的库的链接。 Deuce STM 顾名思义,它是 Java 的 STM 实现。然后是 ActorFoundry,顾名思义,它是 Java 的 Actor 模型。然而,我忍不住用内置的 Actor 模型为 Scala 制作插件。
I use immutable data structures as much as possible. About the only time I do use mutable structures is when I have to such as with a library that will save a boatload of work. Even then I try to encapsulate that library in an immutable structure. If things can't change then there's less to worry about.
I should add that some things to keep in mind on your future endeavors are STM and Actor models. Both of these approaches to concurrency are showing very good progress. While there is some overhead for each, depending on the nature of your program that might not be an issue.
Edit:
Here are a few links to some libraries you could use in your next project. There's Deuce STM which as the name implies is an STM implementation for Java. Then there's the ActorFoundry which as the name implies is an Actor model for Java. However, I can't help but make the plug for Scala with its built in Actor model.
The fewer threads you have, the smaller state they share, and the simpler their interaction pattern on this shared state, the simpler your life will be.
You say Lists are throwing ConcurrentModificationException. I take it that your lists are acessed by seperate threads. So the first thing you should ask yourself is whether this is necessary. Is it not possible for the second thread to operate on a copy of the list?
If it is indeed necessary for the threads to access the list concurrently, locking the list during the entire traversal might be an option (Iterators are invalidated if the list is modified by any other means than that iterator). Of course, if you do other things while traversing the list, this traversal might take long, and locking out other threads might threaten the liveness of the system.
Also keep in mind that if the list is shared state, so are its contents, so if you intend to circumwent locking by copying the list, be sure to perform a deep copy, or prove that the objects contained in the list are themselves thread safe.
List<Item> items = new ArrayList<Item>();
//... some code adding items to the list
for (Item item : items) {
if(item.isTheOneIWantToRemove()) {
items.remove(item); //This will result in a ConcurrentModificationException
}
}
将 for 循环更改为带有迭代器的循环,或者增加索引值可以解决问题:
for (Iterator<String> it = items.iterator(); it.hasNext();) {
if(item.isTheOneIWantToRemove()) {
it.remove(); //No exception thrown
}
}
或
for (int i = 0; i < items.size(); i++) {
if(item.isTheOneIWantToRemove()) {
items.remove(items.get(i)); //No exception thrown
}
}
It's possible that the multi-threaded nature of your application might be a red herring, with respect to the ConcurrentModificationExceptions you mentioned: there are other ways that you can get a ConcurrentModificationException that don't necessarily involve multiple threads. Consider the following:
List<Item> items = new ArrayList<Item>();
//... some code adding items to the list
for (Item item : items) {
if(item.isTheOneIWantToRemove()) {
items.remove(item); //This will result in a ConcurrentModificationException
}
}
Changing your for loop to a loop with an iterator, or an increasing index value solves the problem:
for (Iterator<String> it = items.iterator(); it.hasNext();) {
if(item.isTheOneIWantToRemove()) {
it.remove(); //No exception thrown
}
}
or
for (int i = 0; i < items.size(); i++) {
if(item.isTheOneIWantToRemove()) {
items.remove(items.get(i)); //No exception thrown
}
}
From the design perspective, I've found it useful to draw sequence diagrams where each thread's actions are color coded (that is, each thread has its own color). Using color in this way may be a non-standard use of a sequence diagram, but it's good for giving an overview of how and where threads interract.
As others have mentioned though, reducing the amount of threading in your design to the absolute minimum it needs to work properly will help a lot as well.
It depends what your threads do. Typically programs have a main thread that does the thinking and worker threads to do parallel tasks (timers, handling long computations on a GUI, etc.) But your app may be different - it depends on your design. What do you use threads for? What locks do you have to protect shared datastructures? If you use multiple locks, do you have a single order in which you lock to prevent deadlocks?
发布评论
评论(7)
并发归结为管理共享状态。
因此,您必须问自己的问题是:
管理共享状态的最简单方法是序列化每个操作。然而,这种粗粒度的方法会导致高锁争用和较差的性能。管理并发可以看作是一种优化练习,您可以在其中尝试减少争用。所以接下来的问题是:
许多减少争用的方法都依赖于强制执行正确行为所必需的内容与减少争用的可行内容之间的某种形式的权衡。
请注意,我从未开发过游戏,只开发过企业应用程序的服务器端部分。我可以想象,情况可能会完全不同。
Concurrency boils down to managing shared state.
So the question you must ask yourself are:
The simplest approach to manage shared state is to serialize every action. This coarse-grained approach results however into a high lock contention and poor performance. Managing concurrency can be seen an optimization exercise where you try to reduce the contention. So subsequent questions are:
A lot of approach to reduce contention rely on some form of trade-off between what would be necessary to enforce the correct behavior and what is feasible to reduce contention.
Note that I never worked on a game, only on server-side part of enterprise apps. I can imagine that it can be quite different.
我尽可能使用不可变的数据结构。我唯一一次使用可变结构是当我必须使用可变结构时,例如使用可以节省大量工作的库。即便如此,我还是尝试将该库封装在不可变的结构中。如果事情无法改变,那就不用担心了。
我应该补充一点,在你未来的努力中需要记住的一些事情是 STM 和 Actor 模型。这两种并发方法都显示出非常好的进展。虽然每个都有一些开销,但这取决于程序的性质,这可能不是问题。
编辑:
以下是一些您可以在下一个项目中使用的库的链接。 Deuce STM 顾名思义,它是 Java 的 STM 实现。然后是 ActorFoundry,顾名思义,它是 Java 的 Actor 模型。然而,我忍不住用内置的 Actor 模型为 Scala 制作插件。
I use immutable data structures as much as possible. About the only time I do use mutable structures is when I have to such as with a library that will save a boatload of work. Even then I try to encapsulate that library in an immutable structure. If things can't change then there's less to worry about.
I should add that some things to keep in mind on your future endeavors are STM and Actor models. Both of these approaches to concurrency are showing very good progress. While there is some overhead for each, depending on the nature of your program that might not be an issue.
Edit:
Here are a few links to some libraries you could use in your next project. There's Deuce STM which as the name implies is an STM implementation for Java. Then there's the ActorFoundry which as the name implies is an Actor model for Java. However, I can't help but make the plug for Scala with its built in Actor model.
你拥有的线程越少,它们共享的状态就越小,并且它们在这个共享状态上的交互模式越简单,你的生活就会越简单。
你说列表抛出 ConcurrentModificationException。我认为您的列表是通过单独的线程访问的。所以你应该问自己的第一件事是这是否有必要。难道第二个线程就不能操作列表的副本吗?
如果线程确实需要同时访问列表,那么在整个遍历过程中锁定列表可能是一个选项(如果通过迭代器以外的任何其他方式修改列表,则迭代器将失效)。当然,如果您在遍历列表时执行其他操作,则遍历可能会花费很长时间,并且锁定其他线程可能会威胁系统的活跃性。
另请记住,如果列表是共享状态,则其内容也是共享状态,因此如果您打算通过复制列表来规避锁定,请务必执行深层复制,或者证明列表中包含的对象本身是线程安全的。
The fewer threads you have, the smaller state they share, and the simpler their interaction pattern on this shared state, the simpler your life will be.
You say Lists are throwing ConcurrentModificationException. I take it that your lists are acessed by seperate threads. So the first thing you should ask yourself is whether this is necessary. Is it not possible for the second thread to operate on a copy of the list?
If it is indeed necessary for the threads to access the list concurrently, locking the list during the entire traversal might be an option (Iterators are invalidated if the list is modified by any other means than that iterator). Of course, if you do other things while traversing the list, this traversal might take long, and locking out other threads might threaten the liveness of the system.
Also keep in mind that if the list is shared state, so are its contents, so if you intend to circumwent locking by copying the list, be sure to perform a deep copy, or prove that the objects contained in the list are themselves thread safe.
就您提到的 ConcurrentModificationExceptions 而言,您的应用程序的多线程性质可能会引起人们的注意:还有其他方法可以获取 ConcurrentModificationException,但不一定涉及多个线程。
考虑以下事项:
将 for 循环更改为带有迭代器的循环,或者增加索引值可以解决问题:
或
It's possible that the multi-threaded nature of your application might be a red herring, with respect to the ConcurrentModificationExceptions you mentioned: there are other ways that you can get a ConcurrentModificationException that don't necessarily involve multiple threads.
Consider the following:
Changing your for loop to a loop with an iterator, or an increasing index value solves the problem:
or
从设计的角度来看,我发现绘制序列图很有用,其中每个线程的操作都用颜色编码(即每个线程都有自己的颜色)。以这种方式使用颜色可能是序列图的非标准使用,但它有助于概述线程如何以及在何处交互。
正如其他人所提到的,将设计中的线程数量减少到正常工作所需的绝对最小值也会有很大帮助。
From the design perspective, I've found it useful to draw sequence diagrams where each thread's actions are color coded (that is, each thread has its own color). Using color in this way may be a non-standard use of a sequence diagram, but it's good for giving an overview of how and where threads interract.
As others have mentioned though, reducing the amount of threading in your design to the absolute minimum it needs to work properly will help a lot as well.
这取决于你的线程做什么。通常,程序有一个主线程来执行思考,工作线程来执行并行任务(计时器、在 GUI 上处理长时间计算等),但您的应用程序可能会有所不同 - 这取决于您的设计。你用线程做什么?您需要什么锁来保护共享数据结构?如果您使用多个锁,是否有一个锁定顺序来防止死锁?
It depends what your threads do. Typically programs have a main thread that does the thinking and worker threads to do parallel tasks (timers, handling long computations on a GUI, etc.) But your app may be different - it depends on your design. What do you use threads for? What locks do you have to protect shared datastructures? If you use multiple locks, do you have a single order in which you lock to prevent deadlocks?