评:
[quote]Holder.count.set(0) 会出现 ABA的问题,new也是解决不了问题的
除非假设 代码块执行时间长些,或者对时间的控制更精确
new 临时解决了问题 只是说明 执行new操作 cpu花费的时间长一些
假如同步代码块内假如等待3秒代码,set(0)也可以实现此需求[/quote]
需求:某代码块要求每5秒只进入一次,并且在5秒边界处存在高并发。
public class Test {
public static void main(String[] args) throws InterruptedException {
final CyclicBarrier barrier = new CyclicBarrier(50); // 50个并发
final long interval = 5000; // 每5秒
Holder.time.set(System.currentTimeMillis());// 起始时间
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
while (true) {
long now = System.currentTimeMillis();
// 时间原子操作+原子计数器实现无锁单线程进入
if (now - Holder.time.get() > interval && Holder.count.incrementAndGet() == 1) {
System.out.println("function block entered");
Holder.time.set(now);
Holder.count = new AtomicLong();
}
}
}
}.start();
}
}
static class Holder {
public static volatile AtomicLong time = new AtomicLong();
public static AtomicLong count = new AtomicLong();
}
}
其实思路也比较简单,首先通过计数器保证同一时刻只能进入一个线程,然后重置时间,最后重置计数器,亮点是计数器的重置只能通过new,不能set(0),set(0)会导致同一时刻其余并发线程看到0值,从而误进入代码块。而new可以保证其他并发线程一直hold在老的对象上累加,new只对后面的新线程起到可见性(volatile),加上之前的时间已经重置,条件判断里能严格保证代码块只进入一次。
当然,这种情况也只能保证99.9%的场景,在多核场景下如果系统做了CPU指令重排序后,那就有可能不止一次进入,我通过压测1000个线程,就出现过1次进入了2次。但基本也满足我的场景需要了。