【问题标题】:Thread Safe Get and Put method of Map (Performance Testing)Map的线程安全Get和Put方法(性能测试)
【发布时间】:2013-02-01 19:03:04
【问题描述】:

我正在尝试对我的服务进行一些性能测试。所以我为此编写了一个多线程程序。它将以少量线程并行访问我的服务,然后测量每个线程返回所需的时间。

我进行更新和进入地图的方式将是线程安全的。正确的?因为我发现很难调试这个多线程程序来查看我的程序是否正常工作。谁能帮我解决这个多线程程序

private static ConcurrentHashMap<Long, Long> histogram = new ConcurrentHashMap<Long, Long>();

    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1 * 1000; i++) {
            service.submit(new ThreadTask(i, histogram));
        }

        service.shutdown();

        while (!service.isTerminated()) {

        }

        ThreadTask.report();
    }


class ThreadTask implements Runnable {
    private int id;
    private RestTemplate restTemplate = new RestTemplate();
    private String result;
    private static ConcurrentHashMap<Long, Long> mapData;

    public ThreadTask(int id, ConcurrentHashMap<Long, Long> histogram) {
        this.id = id;
        this.mapData = histogram;
    }

    @Override
    public void run() {

            long start_time = System.currentTimeMillis();

            result = restTemplate.getForObject("",  String.class);
            long difference = (System.currentTimeMillis() - start_time);

            Long count = getMethod(mapData, difference);
            if (count != null) {
                count++;
                putMethod(mapData, difference, count);
            } else {
                putMethod(mapData, difference, Long.valueOf(1L));
            }

    }

    private synchronized void putMethod(ConcurrentHashMap<Long, Long> hg2, long difference, Long count) {
        hg2.put(Long.valueOf(difference), count);       
    }

    private synchronized Long getMethod(ConcurrentHashMap<Long, Long> hg2, long difference) {
        return hg2.get(difference);
    }

    public static void report() {
        System.out.println(mapData);
    }
}

根据以下建议更新代码-

    private static RestTemplate restTemplate = new RestTemplate();
    private static String result = null;
    private static ConcurrentHashMap<Long, AtomicLong> histogram = new ConcurrentHashMap<Long, AtomicLong>();

    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1 * 1000; i++) {
            service.submit(new ThreadTask(i, histogram));
        }

        service.shutdown();

        while (!service.isTerminated()) {

        }

        ThreadTask.report();
    }


class ThreadTask implements Runnable {
    private int id;
    private static RestTemplate restTemplate = new RestTemplate();
    private String result;
    private static ConcurrentHashMap<Long, AtomicLong> hg;

    public ThreadTask(int id, ConcurrentHashMap<Long, AtomicLong> histogram) {
        this.id = id;
        this.hg = histogram;
    }

    @Override
    public void run() {

            long start_time = System.currentTimeMillis();

            result = restTemplate.getForObject("",  String.class);
            long difference = (System.currentTimeMillis() - start_time);

        final AtomicLong before = hg.putIfAbsent(difference, new AtomicLong(1L));
        if (before != null) {
            before.incrementAndGet();
        }

    }

    public static void report() {
        System.out.println(mapData);
    }
}

谁能看一下,让我知道这次我做对了吗?

【问题讨论】:

  • 只是一个建议,为什么不使用番石榴缓存呢?写入后有一种驱逐方法,这可能正是您所需要的
  • 另外,我不会将你的整个班级都捆绑在一种实现类型上。例如,不采用 ConcurrentHashMap,而是采用 ConcurrentMap。接口创建更好的代码!

标签: java multithreading performance-testing


【解决方案1】:

不,您的代码绝对不是线程安全的,因为整个更新操作不是原子的。你读取一个值,增加它,然后写回。到那时,另一个线程可能已经增加了相同的直方图条目,现在您正在保存一个陈旧的值,实际上是“吞下”了一个命中。

我的建议:在整个更新操作中使用synchronized (histogram) { ... }。但是,您不需要单独的同步方法。

如果您想要无锁解决方案,请使用ConcurrentHashMap&lt;Long, AtomicLong&gt; 并使用此代码进行更新:

final Long before = histogram.putIfAbsent(difference, new AtomicLong(1L));
if (before != null) before.incrementAndGet();

【讨论】:

  • 更好的是,将计数设为AtomicLong 并使用原子操作递增。
  • @ataylor 这是我的第一个想法,但你如何初始化它?代码变得非常混乱,不值得。
  • 好点。 guava MapMaker 是一种使用默认值正确创建线程安全映射的好方法。
  • @ataylor 实际上,我想通了,代码也不算太复杂。现在我看到了,我会用它而不是锁。它每次都会浪费一个AtomicLong 的实例,如果你想避免这种情况,那么它会真的变得一团糟。
  • 非常感谢马尔科。我已经在我的代码中提出了你的建议。也用我的新代码更新了问题。如果我做对了,你能看看并建议我。我想学习这些东西,如果代码中还有问题,问题出在哪里。
【解决方案2】:

您不需要将同步与 ConcurrentHashMap 一起使用

您应该通过多次运行代码来预热代码。我会忽略前 10,000 次并运行测试至少 2 到 10 秒。

【讨论】:

  • 感谢彼得的建议。所以你说我不需要同步我的并发哈希映射的 get 和 put 方法?除此之外,我的代码可以很好地获取性能直方图吗?还是有什么漏洞?
  • 并发更新直方图可能会产生严重的开销,除非您的任务相当长。一种更有效的方法是为每个线程创建一个直方图,并在完成时将它们组合起来。我会使用 TLongLongHashMap 来减少创建对象的开销。
  • +1 你的评论比你的回答好 :) 我会把这个建议包含在答案中。
【解决方案3】:

查看 Guava 的 AtomicLongMap

【讨论】:

    猜你喜欢
    • 2011-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-27
    • 1970-01-01
    • 2013-02-10
    • 1970-01-01
    相关资源
    最近更新 更多