如何在Java中实现一个并发循环ticker (counter) ?

时间:2021-04-15 14:33:53

I want to implement a circular counter in Java. The counter on each request should increment (atomically) and on reaching an upper limit should roll over to 0.

我想在Java中实现一个循环计数器。每个请求上的计数器应该递增(原子化地),当达到上限时,计数器应该滚动到0。

What would be the best way to implement this and are there any existing implementations?

实现这一目标的最佳方式是什么?是否存在现有的实现?

8 个解决方案

#1


6  

If you're that worried about contention using either CAS or synchronized then you could consider something more sophisticated like the proposed JSR 166e LongAdder (source, javadoc).

如果您担心使用CAS或synchronized进行争用,那么可以考虑使用JSR 166e LongAdder(源代码,javadoc)之类更复杂的东西。

That's a straightforward counter with low contention on multithreaded access. You could wrap that to expose (current value mod max value). That is, don't store the wrapped value at all.

这是一个简单的计数器,在多线程访问中争用较少。您可以将其打包以公开(当前值对最大值)。也就是说,不要存储包装的值。

#2


19  

It is easy to implement such a counter atop AtomicInteger:

在AtomicInteger上面实现这样的计数器很容易:

public class CyclicCounter {

    private final int maxVal;
    private final AtomicInteger ai = new AtomicInteger(0);

    public CyclicCounter(int maxVal) {
        this.maxVal = maxVal;
    }

    public int cyclicallyIncrementAndGet() {
        int curVal, newVal;
        do {
          curVal = this.ai.get();
          newVal = (curVal + 1) % this.maxVal;
        } while (!this.ai.compareAndSet(curVal, newVal));
        return newVal;
    }

}

#3


6  

With Java 8

与Java 8

public class CyclicCounter {

    private final int maxVal;
    private final AtomicInteger counter = new AtomicInteger(0);

    public CyclicCounter(int maxVal) {
      this.maxVal = maxVal;
    }

    return counter.accumulateAndGet(1, (index, inc) -> {
        return ++index >= maxVal ? 0 : index;
    });      

}

}

#4


2  

I personally think the AtomicInteger solution is a little ugly as it introduces a race-condition which means your update attempt could "fail" and have to be repeated (by iterating within the while loop) making the update time less deterministic than performing the entire operation within a critical section.

我个人认为AtomicInteger解决方案有点糟糕,因为它引入了一个race条件,这意味着您的更新尝试可能会“失败”,并且必须重复(通过在while循环中迭代),这使得更新时间比在关键部分执行整个操作更不确定。

Writing your own counter is so trivial I'd recommend that approach. It's nicer from an OO-perspective too as it only exposes the operations you're allowed to perform.

写你自己的计数器是如此的琐碎,我推荐这种方法。从oo的角度来看,它也更好,因为它只公开允许执行的操作。

public class Counter {
  private final int max;
  private int count;

  public Counter(int max) {
    if (max < 1) { throw new IllegalArgumentException(); }

    this.max = max;
  }

  public synchronized int getCount() {
    return count;
  }

  public synchronized int increment() {
    count = (count + 1) % max;
    return count;
  }
}

EDIT

编辑

The other problem I perceive with the while loop solution is that given a large number of threads attempting to update the counter you could end up with a situation where you have several live threads spinning and attempting to update the counter. Given that only 1 thread would succeed, all other threads would fail causing them to iterate and waste CPU cycles.

我在while循环解决方案中看到的另一个问题是,给定大量试图更新计数器的线程,您可能会遇到这样的情况:有多个活动线程在旋转,并试图更新计数器。假设只有一个线程会成功,那么所有其他线程都会失败,从而导致它们迭代和浪费CPU周期。

#5


2  

If you use the modulus operator, you could just increment and return the modulus. Unfortunately the modulus operator is expensive, so I encourage other solutions where performance is important.

如果你使用模量算符,你可以增加和返回模量。不幸的是,模运算符很昂贵,所以我鼓励其他性能很重要的解决方案。

public class Count {
    private final AtomicLong counter = new AtomicLong();
    private static final long MAX_VALUE = 500;
    public long getCount() {
        return counter.get() % MAX_VALUE;
    }
    public long incrementAndGet(){
        return counter.incrementAndGet() % MAX_VALUE;

    }
}

You would have to solve the Long.MAX_VALUE case as well.

你需要解长。MAX_VALUE案例。

#6


1  

You can use the java.util.concurrent.atomic.AtomicInteger class to increment atomically. As for setting an upper bound and rolling back to 0, you'll need to do that externally...perhaps encapsulating all this within your own wrapper class.

您可以使用java.util.concurrent.atomic。以原子方式递增的AtomicInteger类。对于设置上限并回滚到0,您需要在外部执行……也许将所有这些封装在您自己的包装类中。

Actually, it appears that you can use compareAndSet to check the upper bound, and then roll over to 0.

实际上,您可以使用compareAndSet检查上限,然后滚动到0。

#7


1  

I have to create a similar circular ticker for a custom Akka Routing logic which had to be different than the default ones to avoid network overhead, since my logic is just to pick the next routee.

我必须为定制的Akka路由逻辑创建一个类似的圆形标记,它必须与默认的标记不同,以避免网络开销,因为我的逻辑只是选择下一个路由。

Note: Copied from the suggested Java 8 implementation:

注意:从建议的Java 8实现中复制:

import akka.routing.Routee;
import akka.routing.RoutingLogic;
import scala.collection.immutable.IndexedSeq;

import java.util.concurrent.atomic.AtomicInteger;

public class CircularRoutingLogic implements RoutingLogic {

  final AtomicInteger cycler = new AtomicInteger();

  @Override
  public Routee select(Object message, IndexedSeq<Routee> routees) {
    final int size = routees.size();
    return size == 0 ? null : routees.apply(cycler.getAndUpdate(index -> ++index < size ? index : 0));
  }
}

#8


0  

For highly-intensive circular counter incremented by multiple threads in parallel, I would recommend using LongAdder (since java 8, see the core idea inside Striped64.java), because it is more scalable compared to AtomicLong. It is easy to adapt it to the above solutions.

对于并行递增的循环计数器,我建议使用LongAdder(因为java 8,请参阅Striped64.java中的核心思想),因为与AtomicLong相比,它更具有可扩展性。它很容易适应上述解决方案。

It is assumed that get operation is not so frequent in LongAdder. When calling counter.get, apply to it 'counter.get % max_number'. Yes, modulo-operation is expensive, but it is infrequent for this use-case, which should amortize the total performance cost.

假设在长三角函数中get操作不是那么频繁。当调用计数器。get, apply to it 'counter。% max_number '。是的,modulo操作很昂贵,但是对于这个用例来说是不常见的,它应该摊销整个性能成本。

Remember though, that get operation is non-blocking, nor atomic.

记住,get操作不是阻塞操作,也不是原子操作。

#1


6  

If you're that worried about contention using either CAS or synchronized then you could consider something more sophisticated like the proposed JSR 166e LongAdder (source, javadoc).

如果您担心使用CAS或synchronized进行争用,那么可以考虑使用JSR 166e LongAdder(源代码,javadoc)之类更复杂的东西。

That's a straightforward counter with low contention on multithreaded access. You could wrap that to expose (current value mod max value). That is, don't store the wrapped value at all.

这是一个简单的计数器,在多线程访问中争用较少。您可以将其打包以公开(当前值对最大值)。也就是说,不要存储包装的值。

#2


19  

It is easy to implement such a counter atop AtomicInteger:

在AtomicInteger上面实现这样的计数器很容易:

public class CyclicCounter {

    private final int maxVal;
    private final AtomicInteger ai = new AtomicInteger(0);

    public CyclicCounter(int maxVal) {
        this.maxVal = maxVal;
    }

    public int cyclicallyIncrementAndGet() {
        int curVal, newVal;
        do {
          curVal = this.ai.get();
          newVal = (curVal + 1) % this.maxVal;
        } while (!this.ai.compareAndSet(curVal, newVal));
        return newVal;
    }

}

#3


6  

With Java 8

与Java 8

public class CyclicCounter {

    private final int maxVal;
    private final AtomicInteger counter = new AtomicInteger(0);

    public CyclicCounter(int maxVal) {
      this.maxVal = maxVal;
    }

    return counter.accumulateAndGet(1, (index, inc) -> {
        return ++index >= maxVal ? 0 : index;
    });      

}

}

#4


2  

I personally think the AtomicInteger solution is a little ugly as it introduces a race-condition which means your update attempt could "fail" and have to be repeated (by iterating within the while loop) making the update time less deterministic than performing the entire operation within a critical section.

我个人认为AtomicInteger解决方案有点糟糕,因为它引入了一个race条件,这意味着您的更新尝试可能会“失败”,并且必须重复(通过在while循环中迭代),这使得更新时间比在关键部分执行整个操作更不确定。

Writing your own counter is so trivial I'd recommend that approach. It's nicer from an OO-perspective too as it only exposes the operations you're allowed to perform.

写你自己的计数器是如此的琐碎,我推荐这种方法。从oo的角度来看,它也更好,因为它只公开允许执行的操作。

public class Counter {
  private final int max;
  private int count;

  public Counter(int max) {
    if (max < 1) { throw new IllegalArgumentException(); }

    this.max = max;
  }

  public synchronized int getCount() {
    return count;
  }

  public synchronized int increment() {
    count = (count + 1) % max;
    return count;
  }
}

EDIT

编辑

The other problem I perceive with the while loop solution is that given a large number of threads attempting to update the counter you could end up with a situation where you have several live threads spinning and attempting to update the counter. Given that only 1 thread would succeed, all other threads would fail causing them to iterate and waste CPU cycles.

我在while循环解决方案中看到的另一个问题是,给定大量试图更新计数器的线程,您可能会遇到这样的情况:有多个活动线程在旋转,并试图更新计数器。假设只有一个线程会成功,那么所有其他线程都会失败,从而导致它们迭代和浪费CPU周期。

#5


2  

If you use the modulus operator, you could just increment and return the modulus. Unfortunately the modulus operator is expensive, so I encourage other solutions where performance is important.

如果你使用模量算符,你可以增加和返回模量。不幸的是,模运算符很昂贵,所以我鼓励其他性能很重要的解决方案。

public class Count {
    private final AtomicLong counter = new AtomicLong();
    private static final long MAX_VALUE = 500;
    public long getCount() {
        return counter.get() % MAX_VALUE;
    }
    public long incrementAndGet(){
        return counter.incrementAndGet() % MAX_VALUE;

    }
}

You would have to solve the Long.MAX_VALUE case as well.

你需要解长。MAX_VALUE案例。

#6


1  

You can use the java.util.concurrent.atomic.AtomicInteger class to increment atomically. As for setting an upper bound and rolling back to 0, you'll need to do that externally...perhaps encapsulating all this within your own wrapper class.

您可以使用java.util.concurrent.atomic。以原子方式递增的AtomicInteger类。对于设置上限并回滚到0,您需要在外部执行……也许将所有这些封装在您自己的包装类中。

Actually, it appears that you can use compareAndSet to check the upper bound, and then roll over to 0.

实际上,您可以使用compareAndSet检查上限,然后滚动到0。

#7


1  

I have to create a similar circular ticker for a custom Akka Routing logic which had to be different than the default ones to avoid network overhead, since my logic is just to pick the next routee.

我必须为定制的Akka路由逻辑创建一个类似的圆形标记,它必须与默认的标记不同,以避免网络开销,因为我的逻辑只是选择下一个路由。

Note: Copied from the suggested Java 8 implementation:

注意:从建议的Java 8实现中复制:

import akka.routing.Routee;
import akka.routing.RoutingLogic;
import scala.collection.immutable.IndexedSeq;

import java.util.concurrent.atomic.AtomicInteger;

public class CircularRoutingLogic implements RoutingLogic {

  final AtomicInteger cycler = new AtomicInteger();

  @Override
  public Routee select(Object message, IndexedSeq<Routee> routees) {
    final int size = routees.size();
    return size == 0 ? null : routees.apply(cycler.getAndUpdate(index -> ++index < size ? index : 0));
  }
}

#8


0  

For highly-intensive circular counter incremented by multiple threads in parallel, I would recommend using LongAdder (since java 8, see the core idea inside Striped64.java), because it is more scalable compared to AtomicLong. It is easy to adapt it to the above solutions.

对于并行递增的循环计数器,我建议使用LongAdder(因为java 8,请参阅Striped64.java中的核心思想),因为与AtomicLong相比,它更具有可扩展性。它很容易适应上述解决方案。

It is assumed that get operation is not so frequent in LongAdder. When calling counter.get, apply to it 'counter.get % max_number'. Yes, modulo-operation is expensive, but it is infrequent for this use-case, which should amortize the total performance cost.

假设在长三角函数中get操作不是那么频繁。当调用计数器。get, apply to it 'counter。% max_number '。是的,modulo操作很昂贵,但是对于这个用例来说是不常见的,它应该摊销整个性能成本。

Remember though, that get operation is non-blocking, nor atomic.

记住,get操作不是阻塞操作,也不是原子操作。