【问题标题】:incrementAndGet method of AtomicLong is blocking call?AtomicLong 的 incrementAndGet 方法阻塞调用?
【发布时间】:2013-04-13 05:18:36
【问题描述】:

我正在研究Multithreaded code,我正在尝试测量一个特定方法所花费的时间,因为我正在尝试对我们的大多数队友代码进行基准测试,就像我正在做Load and Performance 测试我们的Client code 和然后是我们的Service code

所以对于这个性能测量,我正在使用-

System.nanoTime();

我有多线程代码,我从中生成多个线程并尝试测量该代码花费了多少时间。

下面是我试图测量任何代码性能的示例示例-在下面的代码中我试图测量-

beClient.getAttributes method

下面是代码-

public class BenchMarkTest {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newFixedThreadPool(5);

        try {

            for (int i = 0; i < 3 * 5; i++) {
                executor.submit(new ThreadTask(i));
            }

            executor.shutdown();
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        } catch (InterruptedException e) {

        }
    }

}

下面是实现Runnable接口的类

class ThreadTask implements Runnable {
    private int id;
    public static ConcurrentHashMap<Long, AtomicLong> selectHistogram = new ConcurrentHashMap<Long, AtomicLong>();


    public ThreadTask(int id) {
        this.id = id;
    }

    @Override
    public void run() {


        long start = System.nanoTime();

        attributes = beClient.getAttributes(columnsList);

        long end = System.nanoTime() - start;

        final AtomicLong before = selectHistogram.putIfAbsent(end / 1000000L, new AtomicLong(1L));
        if (before != null) {
            before.incrementAndGet();
        }
    }
}

无论我想测量什么代码,我通常将下面的行放在该方法的上方

long start = System.nanoTime();

这两行在相同的方法之后,但ConcurrentHashMap不同

long end = System.nanoTime() - start;

final AtomicLong before = selectHistogram.putIfAbsent(end / 1000000L, new AtomicLong(1L));
        if (before != null) {
            before.incrementAndGet();
        }

今天我和我的一位资深人士开会,他说ConcurrentHashMapincrementAndGet 方法是一个阻塞调用。所以你的线程会在那里等待一段时间。

他让我做那个Asynchronous call

是否有可能进行异步调用?

因为在我们所有的客户端代码和服务代码中来衡量每个方法的性能,我使用上面相同的三行,我通常在每个方法之前和之后放置来衡量这些方法的性能。程序完成后,我将这些地图的结果打印出来。

所以现在我正在考虑制作Asynchronous call?谁能帮我做这件事?

基本上,我正在尝试以异步方式测量特定方法的性能,以便每个线程都不会等待并被阻塞。

我想,我可以使用Futures 来做到这一点。谁能提供一个相关的例子?

感谢您的帮助。

【问题讨论】:

  • 我在这里搞糊涂了,incrementAndGet 不是ConcurrentHashMap 的方法,而是AtomicLong 的方法。这是你的问题吗?
  • 是的。我不知何故搞砸了。只需通过更改更新问题即可。
  • @TechGeeky 我对答案进行了更新,可能会提高您代码的性能。如果您愿意,请查看并使用它。干杯。
  • 谢谢。您可以相应地更新答案吗?这样,我就不会错过任何关键的东西。如果我尝试这样理解,可能会错过代码中的一些重要内容。感谢您的帮助。
  • @TechGeeky 你去吧。在“完整更新的代码”下方查看。

标签: java asynchronous atomic concurrenthashmap


【解决方案1】:

行:

if (before != null) {
    before.incrementAndGet();
}

会锁定当前线程直到before.incrementAndGet()获得锁(如果一定要知道,其实没有锁,有while(true)compare-and-swap方法)并返回long值(即你是不使用)。

您可以通过在它自己的线程中调用该特定方法来使其异步,从而不会阻塞当前线程。

要做到这一点,我相信你已经知道如何做到这一点:使用 Thread.start()ExecutorServiceFutureTask(查看“How to asynchronously call a method in Java”,了解如何以优雅的方式进行操作)。

如果我不清楚,这是使用FutureTask的解决方案:

public class BenchMarkTest {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newFixedThreadPool(5);

        int threadNum = 2;
        ExecutorService taskExecutor = Executors.newFixedThreadPool(threadNum);
        List<FutureTask<Long>> taskList = new ArrayList<FutureTask<Long>>();

        try {

            for (int i = 0; i < 3 * 5; i++) {
                executor.submit(new ThreadTask(i, taskExecutor, taskList));
            }

            executor.shutdown();
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        } catch (InterruptedException e) {

        }

        for (FutureTask<Long> futureTask : taskList) {
            futureTask.get(); // doing a job similar to joining threads
        }
        taskExecutor.shutdown();
    }

}

ThreadTask类:

class ThreadTask implements Runnable {
    private int id;
    public static ConcurrentHashMap<Long, AtomicLong> selectHistogram = new ConcurrentHashMap<Long, AtomicLong>();

    private ExecutorService taskExecutor;
    private List<FutureTask<Long>> taskList;    

    public ThreadTask(int id, ExecutorService taskExecutor, List<FutureTask<Long>> taskList) {
        this.id = id;
        this.taskExecutor = taskExecutor;
        this.taskList = taskList;
    }

    @Override
    public void run() {


        long start = System.nanoTime();

        attributes = beClient.getAttributes(columnsList);

        long end = System.nanoTime() - start;

        final AtomicLong before = selectHistogram.putIfAbsent(end / 1000000L, new AtomicLong(1L));
        if (before != null) {
            FutureTask<Long> futureTask = new FutureTask<Long>(new Callable<Long>() {
                public Long call() {
                    return before.incrementAndGet();
                }
            });
            taskList.add(futureTask);
            taskExecutor.execute(futureTask);
        }
    }
}

更新:

我想到了一点可能的改进:与其在ThreadTask 类中告诉taskExecutor 执行futureTask,不如将​​任务的执行推迟到main 方法的末尾.我的意思是:

去掉ThreadTask.run()下面的那一行:

            taskExecutor.execute(futureTask);

并且,在main() 方法中,您有:

        for (FutureTask<Long> futureTask : taskList) {
            futureTask.get(); // doing a job similar to joining threads
        }
        taskExecutor.shutdown();

添加任务的执行,从而有:

        taskExecutor.invokeAll(taskList);
        for (FutureTask<Long> futureTask : taskList) {
            futureTask.get(); // doing a job similar to joining threads
        }
        taskExecutor.shutdown();

(另外,您可以删除ThreadTaskExecutorService 字段,因为它将不再使用它。)

这样,在执行基准测试时开销很小(开销是将对象添加到taskList,仅此而已)。

完整更新的代码:

public class BenchMarkTest {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newFixedThreadPool(5);

        List<FutureTask<Long>> taskList = new ArrayList<FutureTask<Long>>();

        try {

            for (int i = 0; i < 3 * 5; i++) {
                executor.submit(new ThreadTask(i, taskList));
            }

            executor.shutdown();
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        } catch (InterruptedException e) {

        }

        int threadNum = 2;
        ExecutorService taskExecutor = Executors.newFixedThreadPool(threadNum);
        taskExecutor.invokeAll(taskList);
        for (FutureTask<Long> futureTask : taskList) {
            futureTask.get(); // doing a job similar to joining threads
        }
        taskExecutor.shutdown();
    }

}

-

class ThreadTask implements Runnable {
    private int id;
    public static ConcurrentHashMap<Long, AtomicLong> selectHistogram = new ConcurrentHashMap<Long, AtomicLong>();

    private List<FutureTask<Long>> taskList;    

    public ThreadTask(int id, List<FutureTask<Long>> taskList) {
        this.id = id;
        this.taskList = taskList;
    }

    @Override
    public void run() {


        long start = System.nanoTime();

        attributes = beClient.getAttributes(columnsList);

        long end = System.nanoTime() - start;

        final AtomicLong before = selectHistogram.putIfAbsent(end / 1000000L, new AtomicLong(1L));
        if (before != null) {
            FutureTask<Long> futureTask = new FutureTask<Long>(new Callable<Long>() {
                public Long call() {
                    return before.incrementAndGet();
                }
            });
            taskList.add(futureTask);
        }
    }
}

【讨论】:

  • 这是有道理的acdcjunior。谢谢您的帮助。我也有同样的印象,我也应该使用 Futures。有没有可能你也可以为未来提供一个例子?
  • 你去。我会再编辑一点,展示你最终的 main()。
  • 是的,那将是很棒的 acdcjunior。我对当前事物的设置方式有些困惑。另外我对期货也有点陌生。感谢您的帮助。
  • 就是这样。让我知道它是否有效(可能有一些拼写错误,因为我在这里没有使用编译器)。另外,如果你不想改变ThreadTask的构造函数,你可以把taskExecutortaskListstatic改成这样使用。
  • 只是为了明确AtomicInteger.incrementAndGet() 从不获取锁,因为它是基于 CAS(比较和切换)的方法。
【解决方案2】:

我确信递增AtomicLong 比创建Runnable/Callable 对象并将其传递给ExecutorService 所需的时间更少。这会导致你真的成为瓶颈吗?如果您真的想要快速并发增量,请参阅 JDK8 中的 LongAdder

LongAdders 可以与 ConcurrentHashMap 一起使用,以维护可扩展的频率图(直方图或多重集的一种形式)。例如,要向 ConcurrentHashMap freqs 添加计数,如果不存在则进行初始化,您可以使用 freqs.computeIfAbsent(k -> new LongAdder()).increment();

【讨论】:

  • 可能是这样。但这是一个恒定的时间。此外,他正在寻求有关如何通过异步调用进行增量的帮助。创建一个新的可运行/可调用/等是唯一的方法。
  • 我不确定是什么导致了瓶颈。但是我试图避免所有的可能性,比如线程等待它或任何其他问题,当我试图测量客户端代码和服务代码的每个方法的性能时,这些问题会增加我这边的时间测量。
  • 您如何确定所有增量都已通过这种异步方法及时完成?
  • 这也是我的另一个困惑。我能否从异步方法中获得准确的数字?
  • 你不能确定,因为ExecutorService 可能会抛出异常,而且你不知道所有更新何时准备就绪,除非你正在维护所有 Future 对象的列表并检查它们是否完成的。无论如何,异步只是增加了整体 CPU 时间,因为最后你仍然在增加 AtomicInteger。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-01
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 2020-04-22
  • 2020-01-25
  • 1970-01-01
相关资源
最近更新 更多