Java方面的同步和性能

我刚刚意识到我需要在一个方面同步大量的数据收集代码,但性能是一个真正的问题。 如果性能下降太多,我的工具将被抛弃。 我将单独编写int和long,以及各种数组,ArrayLists和Maps。 将有一个应用程序的多个线程将进行将由我的方面拾取的函数调用。 我应该注意哪些事情会对性能产生负面影响? 什么代码模式更有效?

特别是我有一种方法可以调用许多其他数据记录方法:

void foo() { bar(); woz(); ... } 

这些方法主要是添加方面字段的递增

 void bar() { f++; // f is a field of the aspect for (int i = 0; i < ary.length; i++) { // get some values from aspect point cut if (some condiction) { ary[i] += someValue; // ary a field of the aspect } } } 

我应该单独同步foo,bar,woz和其他人,还是应该将bar,woz等中的所有代码移动到foo中并同步它? 我应该在具体创建的同步对象上进行同步:

 private final Object syncObject = new Object(); 

(请参阅此文章),或方法中的各个数据元素:

 ArrayList a = new ArrayList(); void bar() { synchronize(a) { // synchronized code } } 

并发非常棘手。 这很容易弄错,而且很难做对。 在这一点上,我不会过于担心表现。 我首要关注的是让并发代码安全地工作(没有死锁或竞争条件)。

但在性能问题上:当有疑问时,简介。 很难说同步方案将如何影响性能。 我们更难给你建议。 我们需要查看更多代码,并深入了解应用程序的function,为您提供真正有用的答案。 相比之下,分析为您提供了关于一种方法是否比另一种方法慢的确凿证据。 它甚至可以帮助您确定减速的位置。

如今,Java有很多很棒的分析工具。 Netbeans和Eclipse分析器很好。

另外,我建议完全远离原始同步。 尝试使用java.util.concurrency包中的一些类。 它们使编写并发代码变得更加容易,而且更不容易出错。

另外,我建议你阅读Brian Goetz等人的Java Concurrency in Practice 。 它编写得很好,涵盖了很多方面。

经验法则不是在this同步 – 大多数时候它是性能命中 – 所有方法都在一个对象上同步。

考虑使用锁 – 它们是非常好的抽象和许多优秀的function,例如,试图锁定一段时间,然后放弃:

 if(commandsLock.tryLock(100, TimeUnit.MILLISECONDS)){ try { //Do something }finally{ commandsLock.unlock(); } }else{ //couldnt acquire lock for 100 ms } 

我对使用java.util.concurrent第二意见。 我会做两个同步的同步

  • 同步集合访问(如果需要)
  • 同步字段访问

collections访问

如果你的集合是read-only即没有元素被删除 – 插入(但元素可能会改变)我会说你应该使用同步集合(但这可能不需要……)并且不要同步迭代:

只读:

 for (int i = 0; i < ary.length; i++) { // get some values from aspect point cut if (some condiction) { ary += someValue; // ary a field of the aspect } } 

和ary是Collections.synchronizedList获取的实例。

读写

 synchronized(ary){ for (int i = 0; i < ary.length; i++) { // get some values from aspect point cut if (some condiction) { ary += someValue; // ary a field of the aspect } } } 

或者使用一些并发集合(如CopyOnWriteArrayList ),这些集合是安全的。

主要区别在于 - 在第一个只读的版本中,任何数量的线程都可以遍历此集合,而在第二个中,每次只能迭代一次。 在这两种情况下,一次只有一个therad应该增加任何给定的字段。

现场访问

与同步迭代分开地同步字段上的增量。

喜欢:

  Integer foo = ary.get(ii); synchronized(foo){ foo++; } 

摆脱同步

  1. 使用并发集合(来自java.util.concurrent - 不是来自`Collections.synchronizedXXX',后者仍然需要在遍历上进行同步)。
  2. 使用java.util.atomic可以使您以primefaces方式递增字段。

你应该看的东西:

Java内存模型 - 这是一个很好地理解JAVA中的同步和数据对齐方式的演讲。

Upadte:自从写下面的内容后,我看到你已经略微更新了这个问题。 原谅我的无知 – 我不知道“方面”是什么 – 但是从您发布的示例代码中,您还可以考虑使用primefaces/并发集合(例如AtomicInteger,AtomicIntegerArray)或primefaces字段更新器 。 不过,这可能意味着对代码进行重新分解。 (在关于双进程超线程Xeon的Java 5中,AtomicIntegerArray的吞吐量明显优于同步数组;对不起,我还没有完成对更多procs /更高版本JVM版本的重复测试 – 请注意从那以后,’synchronized’有所改善。)

如果没有关于您的特定计划的更具体的信息或指标,您可以做的最好的就是遵循良好的计划设计 。 值得注意的是,JVM中同步锁的性能和优化已成为过去几年中获得最多研究和关注的领域之一(如果不是,该领域)。 所以在最新版本的JVM中,它并不是那么糟糕。

所以一般来说,我会说最低限度的同步而不会“疯狂” 。 通过’minimally’,我的意思是让你在尽可能少的时间内保持锁定,这样只有需要使用该特定锁的部分才能使用该特定的锁。 但是,只有改变很容易,并且很容易certificate您的程序仍然正确。 例如,而不是这样做:

 synchronized (a) { doSomethingWith(a); longMethodNothingToDoWithA(); doSomethingWith(a); } 

当且仅当您的程序仍然正确时,请考虑这样做:

 synchronized (a) { doSomethingWith(a); } longMethodNothingToDoWithA(); synchronized (a) { doSomethingWith(a); } 

但请记住,带有不必要锁定的奇怪的简单字段更新可能不会产生太大的实际差异,并且实际上可以提高性能。 有时,持有一个锁更长时间并减少锁定“管家”可能是有益的。 但是JVM可以做出一些决定 ,所以你不需要过于偏执 – 只要做一般明智的事情,你应该没事。

通常,尝试为每组方法/访问设置单独的锁,这些方法/访问一起构成“独立进程”。 除此之外,拥有一个单独的锁定对象可以是将锁封装在其使用的类中的好方法(即防止外部调用者以您未预测的方式使用它),但可能没有性能差异本身从使用一个对象到另一个对象作为锁(例如,使用实例本身与私有对象声明只是你建议的那个类中的锁),前提是这两个对象将以完全相同的方式使用。

内置语言构造和库之间应该存在性能差异,但经验告诉我不要猜测性能是什么。

如果您将方面编译到应用程序中,那么基本上没有性能损失,如果您在运行时(负载类型编织)执行此操作,那么您将看到性能损失。

如果您将每个方面都设置为perinstance,那么它可能会减少同步的需要。

您应该尽可能少地同步,以尽可能短的时间,以减少任何问题。

如果可能的话,您可能希望在线程之间尽可能少地共享状态,尽可能保持本地,以减少任何死锁问题。

更多信息将导致更好的答案顺便说一句。 🙂