【问题标题】:Java Performance using Concurrency使用并发的 Java 性能
【发布时间】:2010-08-09 19:57:50
【问题描述】:
  1. 如何提高性能 这段代码?
  2. 给定问题陈述的单元测试用例是什么?

代码:

    public class SlowDictionary {
        private final Map<String,String> dict = new HashMap<String,String>();
        public synchronized String translate (String word)
        throws IllegalArgumentException {
            if (!dict.containsKey(word)) {
                throw new IllegalArgumentException(word + " not found.");
            }
            return dict.get(word);
        }

        public synchronized void addToDictionary (String word, String translation) 
            throws IllegalArgumentException {
            if (dict.containsKey(word)) {
                throw new IllegalArgumentException(word + " already exists.");
            }
            dict.put(word,translation);
        }

        public synchronized Set<String> getAllWords () {    
            return dict.keySet();
        }
    }

【问题讨论】:

    标签: java performance collections concurrency


    【解决方案1】:

    你要做的第一件事就是去掉所有同步的关键词。

    最简单的方法是将 dict 声明为 ConcurrentHashMap:

    private final ConcurrentMap<String,String> dict = new ConcurrentHashMap<String,String>();
    

    这样做您可以立即删除 translate 的同步部分,使其看起来像:

     public String translate (String word) throws IllegalArgumentException { ..
    

    原因是 CCHM 持有的关于最新读取的合同。

    最后,添加到字典可以是这样的:

     public void addToDictionary (String word, String translation) throws IllegalArgumentException {
                if (dict.putIfAbsent(word,translation)!=null) {
                    throw new IllegalArgumentException(word + " already exists.");
                }
            }
    

    同时从 getAllWords 中删除同步。

    编辑:在考虑了汤姆的评论之后。在这种“例外情况”中进行双重查找可能是不值得的。如果case没有抛出异常,那就合适了。

    【讨论】:

    • 我对 addToDictionary 的实现感到困惑。您正在优化引发异常的路径,并使快乐路径的成本增加一倍?
    【解决方案2】:

    转储所有synchronized 关键字并将dict 定义为ConcurrentHashMap 可能值得一试。

    【讨论】:

    • 应该dict 是私人final 类型吗?没有final 会有什么后果?
    • @Tom:我相信 John V. 对 addToDictionary 的重写纠正了与简单地放入 CHM 相关的唯一多线程漏洞(尽管我欢迎更正并请求澄清,如果我错了)。这段代码没有任何删除条目的机制这一事实大大简化了事情。
    • 是的,尽管translate 效率低下,如果添加了删除方法会中断。
    【解决方案3】:
    1. 构造和抛出异常很慢,所以不要这样做。
    2. 确保在每个方法中只使用一个映射操作,而不是加倍查找。
    3. 如果同时大量使用,请使用ConcurrentHashMap 而不是synchronized

    注意,getAllWords 方法不是线程安全的,或者至少,它返回的 Set 不是。

    【讨论】:

    • 但是如果我使用同步而不是 getAllWords 应该是线程安全的吧?
    • 如果不是构造和抛出异常,我该如何处理我想抛出异常的情况?
    • 另外你能详细说明你的第二点吗,我不太清楚?
    • 1.异常多用于异常情况,那么我们为什么要关心它们的性能呢?这是我见过的最奇怪的 java 实践。
    • @Rachel 在性能方面,这取决于您要多久抛出一次异常。如果您想要良好的性能并抛出异常,您将不得不放弃一个或另一个约束。至于第 2 点,您似乎正在对哈希映射进行两次查找(为此,您可能需要ConcurrentHashMap 中的扩展操作)。
    【解决方案4】:

    当您说提高性能时,您对使用情况统计有任何想法吗?例如,多少次写入读取,内部映射有多大?

    如果读取次数成比例地很高,并且映射主要在启动时填充(并且不是很大),那么写入时复制策略可能是您的最佳选择。我们使用(并维护)了一个CopyOnWriteMap,它的并发读取性能比 ConcurrentHashMap 更好(在我们的测试中提高了大约 10%)。

    【讨论】:

    • 我忘了说,链接的 CopyOnWriteMap 还提供了一个稳定的、不可修改的 keySet() 视图,这将解决此处显示的 getAllWords() 实现中的潜在错误——如果有人试图删除( String) 例如,或者在同时修改它时对其进行迭代。
    【解决方案5】:

    您应该使用 ConcurrentHashMap 但是在您当前的实现中,getAllWords() 仅在同步块内对数据具有线程安全副本毫无价值,即除非调用者也同步 collction,否则它不是线程安全的。解决此问题的一种方法是在返回之前获取副本(或使用 ConcurrentHashMap)

    在以下示例中,每个方法访问一次映射,而不是两次。 (不同步)

    public class SlowDictionary { 
        private final ConcurrentMap<String,String> dict = new ConcurentHashMap<String,String>(); 
    
        public String translate (String word) throws IllegalArgumentException { 
            String translation = dict.get(word);
            if (translation == null) 
                throw new IllegalArgumentException(word + " not found."); 
            return translation; 
        } 
    
        public void addToDictionary (String word, String translation) throws IllegalArgumentException { 
            if (dict.putIfAbsent(word, translation) != null) 
                throw new IllegalArgumentException(word + " already exists."); 
        } 
    
        public Set<String> getAllWords () {     
            return dict.keySet(); 
        } 
    }
    

    【讨论】:

    • 这取决于您要测试的内容。你想测试什么情况?
    【解决方案6】:

    我可能会离开这里,但这看起来和你能得到的一样好。这基本上就是千篇一律的同步映射访问器。

    【讨论】:

    • 你能解释一下你所说的cookie切割器同步映射访问器是什么意思吗?
    【解决方案7】:

    如果您的读取次数多于写入次数(通常是这种情况),请考虑使用ReadWriteLock 这样读者就不会互相阻挡。

    【讨论】:

      【解决方案8】:

      很多有效的方法来存储字典。使用重量级的东西,比如 Java 的默认 HashMap 和 String 对象不是其中之一。

      因此,当然,您可以摆脱 synchronized 关键字,并尝试通过解决 Java 特质来左右提高一点速度。

      当然,地图的 contains 是 O(1)...但是当您将数百万个字符串放入其中时调整地图的大小不是 O(1) ;)

      深思熟虑:使用 Trie 来确定单词是否存在可能比简单地计算字符串的哈希码更快(我并不是说你需要的是 trie :我想说的是:不仅仅是“让我们使用 HashMap,它是 O(1),所以你无法击败它”-meets-the-eye。

      而且我可以告诉你,Google 的“翻译”和 Google 的“查找时键入”绝对不是通过在 I-need-constant-resizing- 中存储数百万个 Java 字符串对象来实现的和-I-resize-very-slowly Java HashMaps。

      您有什么要求?多少字?支持多少种语言?

      【讨论】:

      • Java 的 String.hashCode()(至少在 Sun 类中)非常快。所以不要嘲笑这个。
      猜你喜欢
      • 2011-04-07
      • 1970-01-01
      • 2012-08-13
      • 2012-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多