I have a Metrics class that's supposed to keep track of how many transactions we process each second and how long they take. The relevant part of its structure looks like this:
public class Metrics {
AtomicLong sent = new AtomicLong();
AtomicLong totalElapsedMsgTime = new AtomicLong();
AtomicLong sentLastSecond = new AtomicLong();
AtomicLong avgTimeLastSecond = new AtomicLong();
public void outTick(long elapsedMsgTime){
sent.getAndIncrement();
totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
class CalcMetrics extends TimerTask {
@Override
public void run() {
sentLastSecond.set(sent.getAndSet(0));
long tmpElapsed = totalElapsedMsgTime.getAndSet(0);
long tmpSent = sentLastSecond.longValue();
if(tmpSent != 0) {
avgTimeLastSecond.set(tmpElapsed / tmpSent);
} else {
avgTimeLastSecond.set(0);
}
}
}
}
My issue is that the outTick function will get called hundreds of times a second from lots of different threads. Being AtomicLong already ensures that each variable is individually thread safe, and they don't interact with each other in that function, so I don't want a lock that will make one call to outTick block another thread's call to outTick. It's perfectly fine if a couple of different threads increment the sent variable and then they both add to the totalElapsedMsgTime variable.
However, once it gets into CalcMetrics run method (which only happens once each second), they do interact. I want to ensure that I can pick up and reset both of those variables without being in the middle of an outTick call or having another outTick call occur between picking up one variable and the next.
Is there any way of doing this? (Does my explanation even make sense?) Is there a way of saying that A cannot interleave with B but multiple B's can interleave with each other?
EDIT:
I went with the ReadWriteLock that James suggested. Here's what my result looks like for anyone interested:
public class Metrics {
AtomicLong numSent = new AtomicLong();
AtomicLong totalElapsedMsgTime = new AtomicLong();
long sentLastSecond = 0;
long avgTimeLastSecond = 0;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
public void outTick(long elapsedMsgTime) {
readLock.lock();
try {
numSent.getAndIncrement();
totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
finally
{
readLock.unlock();
}
}
class CalcMetrics extends TimerTask {
@Override
public void run() {
long elapsed;
writeLock.lock();
try {
sentLastSecond = numSent.getAndSet(0);
elapsed = totalElapsedMsgTime.getAndSet(0);
}
finally {
writeLock.unlock();
}
if(sentLastSecond != 0) {
avgTimeLastSecond = (elapsed / sentLastSecond);
} else {
avgTimeLastSecond = 0;
}
}
}
}