我在一个应用程序中有2个独立的线程,该线程执行一些图论模拟(使用Java2D),一个线程每秒执行约60次,其任务是渲染。另一个每秒执行约30次,并进行一些计算。
这两个线程共享资源。具体来说,他们共享迭代的集合。当这两个线程同时访问集合时,会发生问题。 (一个试图在其中渲染对象,而另一个正在修改集合)。这将导致ConcurrentModificationException。我尝试了一些解决方案,但我发现一个解决方案工作正常(并且易于实现),正在使用一个空对象,线程切换时将其锁定。
就像这样(示例代码):
public class Question {
static Object syncObject;
public static void main(String []Args) {
syncObject = new Object();
Runnable rendering = new Runnable() { //called 60 times a second
public void run() {
synchronized(syncObject) {
//doRendering
}
}
};
Runnable calculations = new Runnable() { //called 30 times a second
public void run() {
synchronized(syncObject) {
//doModifications
}
}
};
Thread t1 = new Thread(rendering);
Thread t2 = new Thread(calculations);
t1.start();
t2.start();
}
}
现在,这似乎可以正常工作。这样,我不必同步正在使用的每个资源,就可以在渲染时以及“逻辑”计算更新时对其进行修改。
但是我觉得这不是一个很好的解决方案。因此,我想问一下这是否可以接受和/或是否有更合适的解决方案。 (除非我做的是完全错误的)
我也不想重写已经使用一段时间的现有代码的一半。
答案 0 :(得分:0)
您可以使用 any 对象进行同步,因此您所做的正确。但是,java并发包不是使用空对象的固有锁定,而是提供了用于执行此操作的特定锁定对象。参见https://docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html
答案 1 :(得分:0)
如上所实现的那样,全局锁是适用于“粗”锁的良好解决方案。我觉得很适合您的情况。
要对其进行改进,应确保全局锁是不可变的。
private static final Object syncObject = new Object();
此方法的局限性在于,您实际上是在序列化对图形的所有访问。这将并发的可能性限制为一次仅一次。因此,在多cpu机器上,cpus处于闲置状态,而渲染和计算工作可能会花费很长时间。优点是易于实现,并且可以安全地从不同线程进行访问。
其他更难以实现的选项是:1)使用多个细粒度的锁,2)研究“无锁算法”或3)使用变异/发布方法对数据结构进行“不可变”。
以下参考文献讨论了粗粒度锁和细粒度锁:http://fileadmin.cs.lth.se/cs/education/eda015f/2013/herlihy4-5-presentation.pdf
无锁算法可能会涉及到正确实施的问题,并且并非适用于所有情况。可以阅读here。
选择3,使用不可变数据结构具有许多优点(和某些限制)。这意味着图形一旦创建就无法更改。突变将通过创建一个新图来发生。为了减少内存开销,将允许图形的各部分重用。然后,这意味着渲染可以获取图形的副本,并与使图形发生变化的计算完全隔离地进行渲染。突变完成后,可以使用AtomicReference将更新的图形提供给渲染器。缺点是它需要更多的对象分配和更复杂的变异算法,但是,它允许“读取器”线程与单个变异器线程并行工作,从而从中受益。有关更多详细信息,请参见persistent vs immutable data structure,https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html和How does concurrency using immutable/persistent types and data structures work?。