【问题标题】:Java synchronization and performance in an aspectJava同步和性能方面
【发布时间】:2010-10-26 16:11:16
【问题描述】:

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

【问题讨论】:

  • 如果不知道你的每个方法做了什么,真的不可能回答这个问题。你甚至需要同步访问吗?哪些资源可能不是线程安全的?你在访问什么?等
  • 你最后的 sn-p 没有意义。你永远不想在这样的本地创建的对象上进行同步。您同步的对象必须在其他线程中可见,否则没有意义。
  • 对不起,这实际上应该是一个类字段,正在改变。该类实际上也是方面。

标签: java performance synchronization aspectj


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 感谢您的反馈。不幸的是,我无法详细介绍它周围的代码或系统。我敢肯定,当我说我对您的回答不满意时,您不会感到惊讶;),但这是明智的。当我得到结果时,我必须发布我的结果。
  • 好吧,当我说我无法提供有关代码的更多详细信息时,我说得太早了。这是一个方面,方法主要是递增和添加到方面的字段。
【解决方案2】:

更新: 自从写了下面的内容后,我看到你已经稍微更新了这个问题。请原谅我的无知——我不知道什么是“方面”——但从您发布的示例代码中,您还可以考虑使用原子/并发集合(例如 AtomicInteger、AtomicIntegerArray)或atomic field updaters。不过,这可能意味着对您的代码进行相当大的重构。 (在双进程超线程 Xeon 上的 Java 5 中,throughput of AtomicIntegerArray 明显优于同步数组;抱歉,我还没来得及在更多 proc/更高版本的 JVM 上重复测试——请注意,从那时起,“同步”得到了改进。)

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

所以总的来说,我会说同步最少而不会“发疯”。我所说的“最少”是指让您持有锁的时间尽可能短,并且只有需要使用该特定锁的部分才能使用该特定锁。但前提是更改很容易进行并且很容易证明您的程序仍然正确。例如,不要这样做:

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

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

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

但是请记住,带有不必要的锁的奇怪的简单字段更新可能不会产生太大的实际影响,并且实际上可以提高性能。有时,持有锁的时间更长一些,少做一些锁“家务”可能是有益的。但是JVM 可以做出其中的一些决定,因此您不必过于偏执——只要做一些大体上明智的事情就可以了。

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

【讨论】:

  • 非常松散的方面是在其他运行代码中匹配某些条件时调用的代码。见eclipse.org/aspectj
  • 谢谢,我去看看。现在你提到它,我隐约听说过 AspectJ。事实上,它看起来将成为我垃圾箱中“无用框架”的另一个条目(“横切”和“模块化”之类的词通常是警告信号),但我至少会为“常识” “看在上帝的份上。
【解决方案3】:

如果您将方面编译到应用程序中,那么您基本上不会受到性能影响,如果您在运行时执行(负载类型编织),那么您会看到性能受到影响。

如果您对每个方面都有了解,那么它可能会减少同步的需要。

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

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

顺便说一句,更多信息会带来更好的答案。 :)

【讨论】:

    【解决方案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 memory model - 这是一个很好地理解 JAVA 中的同步和数据对齐如何工作的演讲。

    【讨论】:

    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多