5

我刚刚意识到我需要在一个方面同步大量数据收集代码,但性能是一个真正的问题。如果性能下降太多,我的工具将被淘汰。我将分别编写整数和长整数以及各种数组、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 中并同步它?我应该在this, 上同步一个专门创建的同步对象:

private final Object syncObject = new Object();

(见这篇文章),或方法中的单个数据元素:

ArrayList<Integer> a = new ArrayList<Integer>();

void bar() {    
    synchronize(a) {
        // synchronized code
    }
}
4

5 回答 5

9

并发非常棘手。做错很容易,做对也很难。在这一点上,我不会太担心性能。我首先关心的是让并发代码安全地工作(没有死锁或竞争条件)。

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

现在有很多很棒的 Java 分析工具。Netbeans 和 Eclipse 分析器很好。

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

另外,我建议您阅读Brian Goetz 等人的Java Concurrency in Practice 。它写得很好,涵盖了很多领域。

于 2009-05-16T03:20:58.680 回答
4

经验法则是不要同步this——大多数时候它会影响性能——所有方法都在一个对象上同步。

考虑使用锁——它们是一个非常好的抽象和许多优秀的特性,比如尝试锁定一段时间,然后放弃:

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

我对使用java.util.concurrent. 我会做两级同步

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

收藏访问

如果您的集合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 )。

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

现场访问

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

喜欢:

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

摆脱同步

  1. 使用并发集合(来自java.util.concurrent- 而不是来自 `Collections.synchronizedXXX',后者仍然需要在遍历时同步)。
  2. 使用java.util.atomic它使您能够以原子方式递增字段。

你应该看的东西:

Java 内存模型- 它的演讲很好地理解了 JAVA 中的同步和数据对齐是如何工作的。

于 2009-05-16T13:42:49.263 回答
3

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

于 2011-10-26T08:16:20.720 回答
3

Upadte: since writing the below, I see you've updated the question slightly. Forgive my ignorance-- I have no idea what an "aspect" is-- but from the sample code you posted, you could also consider using atomics/concurrent collections (e.g. AtomicInteger, AtomicIntegerArray) or atomic field updaters. This could mean quite a re-factoring of your code, though. (In Java 5 on a dual-proc hyperthreading Xeon, the throughput of AtomicIntegerArray is significantly better than a synchronized array; sorry, I haven't got round to repeating the test on more procs/later JVM version yet-- note that performance of 'synchronized' has improved since then.)

Without more specific information or metrics about your particular program, the best you can do is just follow good program design. It's worth noting that the performance and optimisation of synchronization locks in the JVM has beed one of the areas (if not, the area) that has received most research and attention over the last few years. And so in the latest versions of JVM's, it ain't all that bad.

So in general, I'd say synchronize minimally without "going mad". By 'minimally', I mean so that you hold on to the lock for as less time as possible, and so that only the parts that need to use that specific lock use that specific lock. But only if the change is easy to do and it's easy to prove that your program is still correct. For example, instead of doing this:

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

consider doing this if and only if your program will still be correct:

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

But remember, the odd simple field update with a lock held unnecessarily probably won't make much tangible difference, and could actually improve performance. Sometimes, holding a lock for a bit longer and doing less lock "housekeeping" can be beneficial. But the JVM can make some of those decisions, so you don't need to be tooo paranoid-- just do generally sensible things and you should be fine.

In general, try and have a separate lock for each set of methods/accesses that together form an "independent process". Other than that, having a separate lock object can be a good way of encapsulating the lock within the class it's used by (i.e. preventing it from being used by outside callers in a way you didn't predict), but there's probably no performance difference per se from using one object to another as the lock (e.g. using the instance itself vs a private Object declared just to be a lock within that class as you suggest), provided the two objects would otherwise be used in exactly the same way.

于 2009-05-16T04:13:31.270 回答
1

If you compile the aspect into the application then you will have basically no performance hit, if you do it at runtime (load-type weaving) then you will see a performance hit.

If you have each aspect be perinstance then it may reduce the need for synchronization.

You should have as little synchronization as possible, for as short a time as possible, to reduce any problems.

If possible you may want to share as little state as possible between threads, keeping as much local as possible, to reduce any deadlock problems.

More information would lead to a better answer btw. :)

于 2009-05-16T04:28:42.887 回答