Java方面的同步和性能

时间:2009-05-16 03:07:11

标签: java performance synchronization aspectj

我刚刚意识到我需要在一个方面同步大量的数据收集代码,但性能是一个真正的问题。如果性能下降太多,我的工具将被抛弃。我将单独编写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中并同步它?我应该在this上对特定创建的同步对象进行同步:

private final Object syncObject = new Object();

(请参阅this帖子),或方法中的各个数据元素:

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

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

5 个答案:

答案 0 :(得分:9)

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

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

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

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

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

答案 1 :(得分: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),这些集合绝对是安全的。

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

现场访问

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

喜欢:

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

摆脱同步

  1. 使用并发集合(来自java.util.concurrent - 而不是来自`Collections.synchronizedXXX',后者仍然需要在遍历上进行同步)。
  2. 使用java.util.atomic可以使您以原子方式递增字段。
  3. 你应该看的东西:

    Java memory model - 这是一个非常好的理解,可以很好地理解JAVA中的同步和数据对齐方式。

答案 2 :(得分:3)

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

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

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

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

当且仅当你的程序仍然正确时,才考虑这样做

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

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

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

答案 3 :(得分:3)

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

答案 4 :(得分:1)

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

如果每个方面都是实例,那么可能会减少同步的需要。

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

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

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