Heinz pinged me on skype with some code he'd scraped out of AtomicReference. So before I start with the rant, here is the code.
public final V getAndSet(V newValue) {
while (true) {
V x = get();
if (compareAndSet(x, newValue))
return x;
}
}
So what we have is a wrapper that holds some sort of value object. The compareAndSet maps to the native compareAndSet operation. This operation is suppose to resolve the race condition where by one thread overwrites the values previously written down by another. It does this by you having to know the current value in the target memory location. If you don't know the current value, CAS will fail and this method will return false. The native CAS returns the current value in memory but unfortunately Java converts that to a boolean. But that is another topic. So, if CAS fails that means you didn't know the value in memory and that most likely means that some other thread beat you in changing the value. With this knowledge you can do the right thing. However with AtomicReference it appears as if the default action is to get the current value, CAS down the new value and if it fails, try again. Of course the first step of trying again is to get the current value.
The short story here is that the loser of the race condition to update a memory location will always win. To make matters worse, the bigger the lead the winning thread has, the better change the loser has a obtaining a CAS failure in the first place.
I'm sure that someone out there will explain to me how all of this works and why it is good. But until then, I'm going to say, I don't get it.
Update in case you were interested, all of the Atomic primitive wrappers use the same getAndSet logic.
I think you may be misinterpreting the purpose of the logic. From my
understanding (caveat emptor) this logic is trying to do no more than to
ensure that the return value is the immediate predecessor to the value you
are setting. For example, if you try to set a value, which is currently 4,
to 10 and it didn't have this logic it may return "4" even though between
the read and the write something else set it to 7. So that would return 4
and this would return 7 and the final value would be 10, or that would
return 10 and this would return 4 and the final value would be 7. I don't
think the method or its intended users care about what value "wins", only
that you don't have both of them return 4.
Hi Scott, I get what you're saying as that is my understanding of how is
was suppose to be used. However...
Set to the given value and return the old value.
Parameters:
newValue - the new value
Returns:
the previous value
I have to agree with Scott. It seems to me that the intent is to assure
that no other thread changed the value during the course of the get() and
the CAS. It does that.
Ok, let me rephrase this. What value does CAS in a loop like this have over
volatile?
- in java synchronization allows you to do two things - atomic compound
operation and it ensures visibility of shared data
- volatile is capable of ensuring the visibility issue, but not the atomic operations(one variable depends on the other)
If you would like to implement a counter with volatile variable only you would not succeed. But with AtomicVariables it is as simple as calling incrementAndGet()
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
- anyway the getAndSet method has the same semantic as volatile variable at least as I understand it.
Kirk,
Kirk,
Reference assignment is always atomic and this implementation also uses
volatile. CAS is there to provide a way to change a value in memory with a
means of failing in the case where you'd be "corrupting" an others work.
Yes, a read, or a write of a volatile is atomic. But the read AND the
write is not atomic.
private String myString = "initial value";
public String replaceString(String newValue) {
String oldValue = myString;
myString = newValue;
return oldValue;
}
I don't think the JLS says anything much different that my simple
definition. Also, I'm not interested in the overly pessimistic locking
models we are current forced to use. Database vendors realized a long time
ago that is you want things to scale, you can not use pessimistic locking
models. But using a CAS, we should be able to loosen the model up a bit and
in the process, achieve higher throughput. There is no point in giving me a
data structure that forces me to use synchronization on an outer layer. In
fact, Little's law tells me that would be worse than just using a fully
synchronized data structure.
Indeed, compareAndSet is a better choice. But the question still remains,
why is that method there in the first place?
why is that method there in the first place?
Item i = new Item();
if (!buffer.compareAndSet(null, i)) {
// buffer was full, no consumer is available, we'll process the item ourselves
processItem(i);
}
// we attempt to take an item and mark the buffer as empty
Item i = buffer.getAndSet(null);
if (null != item) {
// there is some work to do
processItem(i);
} else {
// buffer was empty, do something else
doSomeOtherWorkOrGoOnVacation();
}
I wouldn't necessarily say that the example code is contrived. I would say
that as much as it justifies the implementation, it also calls into
question as to where to draw the line when designing a group of classes.
The technical label for this is separation of concerns. The question
remains; is it appropriate for this method to exist in the class? Is this
slam down behavior something that should be in the client code rather than
here? I'd argue yes but not to the death.