Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use AtomicLongFieldUpdater in StepLong, StepDouble, and AtomicDouble #1158

Merged
merged 2 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 Netflix, Inc.
* Copyright 2014-2024 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,7 @@
*/
package com.netflix.spectator.impl;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

/**
* Wrapper around AtomicLong to make working with double values easier.
Expand All @@ -26,22 +26,25 @@
@SuppressWarnings("PMD.MissingSerialVersionUID")
public class AtomicDouble extends Number {

private final AtomicLong value;
private volatile long value;

private static final AtomicLongFieldUpdater<AtomicDouble> VALUE_UPDATER =
AtomicLongFieldUpdater.newUpdater(AtomicDouble.class, "value");

/** Create an instance with an initial value of 0. */
public AtomicDouble() {
this(0.0);
super();
}

/** Create an instance with an initial value of {@code init}. */
public AtomicDouble(double init) {
super();
value = new AtomicLong(Double.doubleToLongBits(init));
this.value = Double.doubleToLongBits(init);
}

/** Return the current value. */
public double get() {
return Double.longBitsToDouble(value.get());
return Double.longBitsToDouble(value);
}

/** Add {@code amount} to the value and return the new value. */
Expand All @@ -51,11 +54,11 @@ public double addAndGet(double amount) {
double n;
long next;
do {
v = value.get();
v = value;
d = Double.longBitsToDouble(v);
n = d + amount;
next = Double.doubleToLongBits(n);
} while (!value.compareAndSet(v, next));
} while (!VALUE_UPDATER.compareAndSet(this, v, next));
return n;
}

Expand All @@ -66,17 +69,17 @@ public double getAndAdd(double amount) {
double n;
long next;
do {
v = value.get();
v = value;
d = Double.longBitsToDouble(v);
n = d + amount;
next = Double.doubleToLongBits(n);
} while (!value.compareAndSet(v, next));
} while (!VALUE_UPDATER.compareAndSet(this, v, next));
return d;
}

/** Set the value to {@code amount} and return the previous value. */
public double getAndSet(double amount) {
long v = value.getAndSet(Double.doubleToLongBits(amount));
long v = VALUE_UPDATER.getAndSet(this, Double.doubleToLongBits(amount));
return Double.longBitsToDouble(v);
}

Expand All @@ -87,15 +90,29 @@ public double getAndSet(double amount) {
public boolean compareAndSet(double expect, double update) {
long e = Double.doubleToLongBits(expect);
long u = Double.doubleToLongBits(update);
return value.compareAndSet(e, u);
return VALUE_UPDATER.compareAndSet(this, e, u);
}

/** Set the current value to {@code amount}. */
public void set(double amount) {
value.set(Double.doubleToLongBits(amount));
value = Double.doubleToLongBits(amount);
}

private static boolean isLessThan(double v1, double v2) {
return v1 < v2 || Double.isNaN(v2);
}

/** Set the current value to the maximum of the current value or the provided value. */
public void min(double v) {
if (Double.isFinite(v)) {
double min = get();
while (isLessThan(v, min) && !compareAndSet(min, v)) {
min = get();
}
}
}

private boolean isGreaterThan(double v1, double v2) {
private static boolean isGreaterThan(double v1, double v2) {
return v1 > v2 || Double.isNaN(v2);
}

Expand Down Expand Up @@ -124,4 +141,8 @@ public void max(double v) {
@Override public double doubleValue() {
return get();
}

@Override public String toString() {
return Double.toString(get());
}
}
128 changes: 111 additions & 17 deletions spectator-api/src/main/java/com/netflix/spectator/impl/StepDouble.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 Netflix, Inc.
* Copyright 2014-2024 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,7 +17,7 @@

import com.netflix.spectator.api.Clock;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

/**
* Utility class for managing a set of AtomicLong instances mapped to a particular step interval.
Expand All @@ -35,41 +35,135 @@ public class StepDouble implements StepValue {
private final long step;

private volatile double previous;
private final AtomicDouble current;
private volatile long current;

private final AtomicLong lastInitPos;
private static final AtomicLongFieldUpdater<StepDouble> CURRENT_UPDATER =
AtomicLongFieldUpdater.newUpdater(StepDouble.class, "current");

private volatile long lastInitPos;

private static final AtomicLongFieldUpdater<StepDouble> LAST_INIT_POS_UPDATER =
AtomicLongFieldUpdater.newUpdater(StepDouble.class, "lastInitPos");

/** Create a new instance. */
public StepDouble(double init, Clock clock, long step) {
this.init = init;
this.clock = clock;
this.step = step;
previous = init;
current = new AtomicDouble(init);
lastInitPos = new AtomicLong(clock.wallTime() / step);
current = Double.doubleToLongBits(init);
lastInitPos = clock.wallTime() / step;
}

private void rollCount(long now) {
final long stepTime = now / step;
final long lastInit = lastInitPos.get();
if (lastInit < stepTime && lastInitPos.compareAndSet(lastInit, stepTime)) {
final double v = current.getAndSet(init);
final long lastInit = lastInitPos;
if (lastInit < stepTime && LAST_INIT_POS_UPDATER.compareAndSet(this, lastInit, stepTime)) {
final double v = Double.longBitsToDouble(
CURRENT_UPDATER.getAndSet(this, Double.doubleToLongBits(init)));
// Need to check if there was any activity during the previous step interval. If there was
// then the init position will move forward by 1, otherwise it will be older. No activity
// means the previous interval should be set to the `init` value.
previous = (lastInit == stepTime - 1) ? v : init;
}
}

/** Get the AtomicDouble for the current bucket. */
public AtomicDouble getCurrent() {
/** Get the value for the current bucket. */
public double getCurrent() {
return getCurrent(clock.wallTime());
}

/** Get the AtomicDouble for the current bucket. */
public AtomicDouble getCurrent(long now) {
/** Get the value for the current bucket. */
public double getCurrent(long now) {
rollCount(now);
return Double.longBitsToDouble(current);
}

/** Set the value for the current bucket. */
public void setCurrent(long now, double value) {
rollCount(now);
current = Double.doubleToLongBits(value);
}

/** Increment the current value and return the result. */
public double addAndGet(long now, double amount) {
rollCount(now);
return current;
long v;
double d;
double n;
long next;
do {
v = current;
d = Double.longBitsToDouble(v);
n = d + amount;
next = Double.doubleToLongBits(n);
} while (!CURRENT_UPDATER.compareAndSet(this, v, next));
return n;
}

/** Increment the current value and return the value before incrementing. */
public double getAndAdd(long now, double amount) {
rollCount(now);
long v;
double d;
double n;
long next;
do {
v = current;
d = Double.longBitsToDouble(v);
n = d + amount;
next = Double.doubleToLongBits(n);
} while (!CURRENT_UPDATER.compareAndSet(this, v, next));
return d;
}

/** Set the current value and return the previous value. */
public double getAndSet(long now, double value) {
rollCount(now);
long v = CURRENT_UPDATER.getAndSet(this, Double.doubleToLongBits(value));
return Double.longBitsToDouble(v);
}

private boolean compareAndSet(double expect, double update) {
long e = Double.doubleToLongBits(expect);
long u = Double.doubleToLongBits(update);
return CURRENT_UPDATER.compareAndSet(this, e, u);
}

/** Set the current value and return the previous value. */
public boolean compareAndSet(long now, double expect, double update) {
rollCount(now);
return compareAndSet(expect, update);
}

private static boolean isLessThan(double v1, double v2) {
return v1 < v2 || Double.isNaN(v2);
}

/** Set the current value to the minimum of the current value or the provided value. */
public void min(long now, double value) {
if (Double.isFinite(value)) {
rollCount(now);
double min = Double.longBitsToDouble(current);
while (isLessThan(value, min) && !compareAndSet(min, value)) {
min = Double.longBitsToDouble(current);
}
}
}

private static boolean isGreaterThan(double v1, double v2) {
return v1 > v2 || Double.isNaN(v2);
}

/** Set the current value to the maximum of the current value or the provided value. */
public void max(long now, double value) {
if (Double.isFinite(value)) {
rollCount(now);
double max = Double.longBitsToDouble(current);
while (isGreaterThan(value, max) && !compareAndSet(max, value)) {
max = Double.longBitsToDouble(current);
}
}
}

/** Get the value for the last completed interval. */
Expand Down Expand Up @@ -97,13 +191,13 @@ public double poll(long now) {

/** Get the timestamp for the end of the last completed interval. */
@Override public long timestamp() {
return lastInitPos.get() * step;
return lastInitPos * step;
}

@Override public String toString() {
return "StepDouble{init=" + init
+ ", previous=" + previous
+ ", current=" + current.get()
+ ", lastInitPos=" + lastInitPos.get() + '}';
+ ", current=" + Double.longBitsToDouble(current)
+ ", lastInitPos=" + lastInitPos + '}';
}
}
Loading
Loading