Lock-free concurrent stack implementation
up vote
0
down vote
favorite
I've been toying around with a simple implementation of a lock-free Stack in Java.
edit: See fixed/working version below
Do you see any issues with this implementation?
Similar implementations in native languages seem to suffer from the ABA problem, but I'm not sure if this is a problem here; obviously no pointer handling done directly in Java, and given all I care for is the end of the stack both for pop and push, I don't see how "missing" any changes in any non-tail element of the stack would cause issues.
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
final AtomicReference<T> top = new AtomicReference<T>(null);
public void push(T item)
T localTop;
do
localTop = top.get();
item.next = localTop;
while(!top.compareAndSet(localTop, item));
public T pop()
T localTop;
do
localTop = top.get();
while(localTop != null && !top.compareAndSet(localTop, localTop.next));
return localTop;
But, here's what I don't get. I've written a simple test that launches a few threads; each one pops items from a pre-existing LockFreeStack and (later, from the same thread that popped it) pushes them back.
After its popped, I increment an atomic counter, and before pushing it back, I decrement it. So I'd always expect the counter to be 0 (right after decrementing / right before pushing back onto the stack) or 1 (right after popping & incrementing).
But, that's not what happens...
public class QueueTest
static class TestStackItem extends LockFreeStack.StackItem<TestStackItem>
final AtomicInteger usageCount = new AtomicInteger(0);
public void inc() throws Exception
int c = usageCount.incrementAndGet();
if(c != 1)
throw new Exception(String.format("Usage count is %d; expected %d", c, 1));
public void dec() throws Exception
int c = usageCount.decrementAndGet();
if(c != 0)
throw new Exception(String.format("Usage count is %d; expected %d", c, 0));
public final LockFreeStack<TestStackItem> testStack = new LockFreeStack<TestStackItem>();
public void test()
final int NUM_THREADS = 4;
for(int i = 0; i < 10; i++)
TestStackItem item = new TestStackItem();
testStack.push(item);
Thread threads = new Thread[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; i++)
threads[i] = new Thread(new TestRunner());
threads[i].setDaemon(true);
threads[i].setName("Thread"+i);
threads[i].start();
while(true)
Thread.yield();
class TestRunner implements Runnable
@Override
public void run()
try
boolean pop = false;
TestStackItem lastItem = null;
while (true)
pop = !pop;
if (pop)
TestStackItem item = testStack.pop();
item.inc();
lastItem = item;
else
lastItem.dec();
testStack.push(lastItem);
lastItem = null;
catch (Exception ex)
System.out.println("exception: " + ex.toString());
throws non-deterministic exceptions, e.g.
exception: java.lang.Exception: Usage count is 1; expected 0
exception: java.lang.Exception: Usage count is 2; expected 1
or from another run
exception: java.lang.Exception: Usage count is 2; expected 0
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 2; expected 1
So some race-condition like issue must be going on here.
What's wrong here - is this indeed ABA-related (and if so, how exactly?) or am I missing anything else?
Thanks!
NOTE: This works, but it doesn't seem to be a great solution. It's neithe garbage-free (StampedAtomicReference creates objects internally), nor does the benefit of being lock free really seem to pay off; in my benchmarks, this wasn't really faster in a single threaded environment, and when testing with 6 threads concurrently, it fell significantly behind just putting locks around the push/pop functions
based on the solution suggested below, this was indeed an ABA problem, and this small change will circumvent that:
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
private final AtomicStampedReference<T> top = new AtomicStampedReference<T>(null, 0);
public void push(T item)
int stampHolder = new int[1];
T localTop;
do
localTop = top.get(stampHolder);
item.next = localTop;
while(!top.compareAndSet(localTop, item, stampHolder[0], stampHolder[0]+1));
public T pop()
T localTop;
int stampHolder = new int[1];
do
localTop = top.get(stampHolder);
while(localTop != null && !top.compareAndSet(localTop, localTop.next, stampHolder[0], stampHolder[0]+1));
return localTop;
java algorithm concurrency
|
show 1 more comment
up vote
0
down vote
favorite
I've been toying around with a simple implementation of a lock-free Stack in Java.
edit: See fixed/working version below
Do you see any issues with this implementation?
Similar implementations in native languages seem to suffer from the ABA problem, but I'm not sure if this is a problem here; obviously no pointer handling done directly in Java, and given all I care for is the end of the stack both for pop and push, I don't see how "missing" any changes in any non-tail element of the stack would cause issues.
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
final AtomicReference<T> top = new AtomicReference<T>(null);
public void push(T item)
T localTop;
do
localTop = top.get();
item.next = localTop;
while(!top.compareAndSet(localTop, item));
public T pop()
T localTop;
do
localTop = top.get();
while(localTop != null && !top.compareAndSet(localTop, localTop.next));
return localTop;
But, here's what I don't get. I've written a simple test that launches a few threads; each one pops items from a pre-existing LockFreeStack and (later, from the same thread that popped it) pushes them back.
After its popped, I increment an atomic counter, and before pushing it back, I decrement it. So I'd always expect the counter to be 0 (right after decrementing / right before pushing back onto the stack) or 1 (right after popping & incrementing).
But, that's not what happens...
public class QueueTest
static class TestStackItem extends LockFreeStack.StackItem<TestStackItem>
final AtomicInteger usageCount = new AtomicInteger(0);
public void inc() throws Exception
int c = usageCount.incrementAndGet();
if(c != 1)
throw new Exception(String.format("Usage count is %d; expected %d", c, 1));
public void dec() throws Exception
int c = usageCount.decrementAndGet();
if(c != 0)
throw new Exception(String.format("Usage count is %d; expected %d", c, 0));
public final LockFreeStack<TestStackItem> testStack = new LockFreeStack<TestStackItem>();
public void test()
final int NUM_THREADS = 4;
for(int i = 0; i < 10; i++)
TestStackItem item = new TestStackItem();
testStack.push(item);
Thread threads = new Thread[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; i++)
threads[i] = new Thread(new TestRunner());
threads[i].setDaemon(true);
threads[i].setName("Thread"+i);
threads[i].start();
while(true)
Thread.yield();
class TestRunner implements Runnable
@Override
public void run()
try
boolean pop = false;
TestStackItem lastItem = null;
while (true)
pop = !pop;
if (pop)
TestStackItem item = testStack.pop();
item.inc();
lastItem = item;
else
lastItem.dec();
testStack.push(lastItem);
lastItem = null;
catch (Exception ex)
System.out.println("exception: " + ex.toString());
throws non-deterministic exceptions, e.g.
exception: java.lang.Exception: Usage count is 1; expected 0
exception: java.lang.Exception: Usage count is 2; expected 1
or from another run
exception: java.lang.Exception: Usage count is 2; expected 0
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 2; expected 1
So some race-condition like issue must be going on here.
What's wrong here - is this indeed ABA-related (and if so, how exactly?) or am I missing anything else?
Thanks!
NOTE: This works, but it doesn't seem to be a great solution. It's neithe garbage-free (StampedAtomicReference creates objects internally), nor does the benefit of being lock free really seem to pay off; in my benchmarks, this wasn't really faster in a single threaded environment, and when testing with 6 threads concurrently, it fell significantly behind just putting locks around the push/pop functions
based on the solution suggested below, this was indeed an ABA problem, and this small change will circumvent that:
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
private final AtomicStampedReference<T> top = new AtomicStampedReference<T>(null, 0);
public void push(T item)
int stampHolder = new int[1];
T localTop;
do
localTop = top.get(stampHolder);
item.next = localTop;
while(!top.compareAndSet(localTop, item, stampHolder[0], stampHolder[0]+1));
public T pop()
T localTop;
int stampHolder = new int[1];
do
localTop = top.get(stampHolder);
while(localTop != null && !top.compareAndSet(localTop, localTop.next, stampHolder[0], stampHolder[0]+1));
return localTop;
java algorithm concurrency
1
@Thilo en.wikipedia.org/wiki/ABA_problem or for lock free stacks described in cs.cmu.edu/~410-s05/lectures/L31_LockFree.pdf
– Bogey
yesterday
Have you try it using sycnh section with regular LinkedList? I think you have a bug in this weird cycle with checking i % 2 == 1
– Anton
yesterday
3
Might I suggest codereview.stackexchange.com as a better site for this question?
– João Mendes
yesterday
1
@JoãoMendes Thanks - wasn't quite sure where to post best as I already know there's a bug in the implementation (or in the test), and the question is rather why instead of if ;-) But will certainly try over there in a bit if that's the better place!
– Bogey
yesterday
1
@Bogey Well, if you're looking for help with a specific bug, then here is probably better. I thought you wanted more global help. I caught the "do you see any issues" line and probably misread the rest of the question... :)
– João Mendes
yesterday
|
show 1 more comment
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I've been toying around with a simple implementation of a lock-free Stack in Java.
edit: See fixed/working version below
Do you see any issues with this implementation?
Similar implementations in native languages seem to suffer from the ABA problem, but I'm not sure if this is a problem here; obviously no pointer handling done directly in Java, and given all I care for is the end of the stack both for pop and push, I don't see how "missing" any changes in any non-tail element of the stack would cause issues.
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
final AtomicReference<T> top = new AtomicReference<T>(null);
public void push(T item)
T localTop;
do
localTop = top.get();
item.next = localTop;
while(!top.compareAndSet(localTop, item));
public T pop()
T localTop;
do
localTop = top.get();
while(localTop != null && !top.compareAndSet(localTop, localTop.next));
return localTop;
But, here's what I don't get. I've written a simple test that launches a few threads; each one pops items from a pre-existing LockFreeStack and (later, from the same thread that popped it) pushes them back.
After its popped, I increment an atomic counter, and before pushing it back, I decrement it. So I'd always expect the counter to be 0 (right after decrementing / right before pushing back onto the stack) or 1 (right after popping & incrementing).
But, that's not what happens...
public class QueueTest
static class TestStackItem extends LockFreeStack.StackItem<TestStackItem>
final AtomicInteger usageCount = new AtomicInteger(0);
public void inc() throws Exception
int c = usageCount.incrementAndGet();
if(c != 1)
throw new Exception(String.format("Usage count is %d; expected %d", c, 1));
public void dec() throws Exception
int c = usageCount.decrementAndGet();
if(c != 0)
throw new Exception(String.format("Usage count is %d; expected %d", c, 0));
public final LockFreeStack<TestStackItem> testStack = new LockFreeStack<TestStackItem>();
public void test()
final int NUM_THREADS = 4;
for(int i = 0; i < 10; i++)
TestStackItem item = new TestStackItem();
testStack.push(item);
Thread threads = new Thread[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; i++)
threads[i] = new Thread(new TestRunner());
threads[i].setDaemon(true);
threads[i].setName("Thread"+i);
threads[i].start();
while(true)
Thread.yield();
class TestRunner implements Runnable
@Override
public void run()
try
boolean pop = false;
TestStackItem lastItem = null;
while (true)
pop = !pop;
if (pop)
TestStackItem item = testStack.pop();
item.inc();
lastItem = item;
else
lastItem.dec();
testStack.push(lastItem);
lastItem = null;
catch (Exception ex)
System.out.println("exception: " + ex.toString());
throws non-deterministic exceptions, e.g.
exception: java.lang.Exception: Usage count is 1; expected 0
exception: java.lang.Exception: Usage count is 2; expected 1
or from another run
exception: java.lang.Exception: Usage count is 2; expected 0
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 2; expected 1
So some race-condition like issue must be going on here.
What's wrong here - is this indeed ABA-related (and if so, how exactly?) or am I missing anything else?
Thanks!
NOTE: This works, but it doesn't seem to be a great solution. It's neithe garbage-free (StampedAtomicReference creates objects internally), nor does the benefit of being lock free really seem to pay off; in my benchmarks, this wasn't really faster in a single threaded environment, and when testing with 6 threads concurrently, it fell significantly behind just putting locks around the push/pop functions
based on the solution suggested below, this was indeed an ABA problem, and this small change will circumvent that:
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
private final AtomicStampedReference<T> top = new AtomicStampedReference<T>(null, 0);
public void push(T item)
int stampHolder = new int[1];
T localTop;
do
localTop = top.get(stampHolder);
item.next = localTop;
while(!top.compareAndSet(localTop, item, stampHolder[0], stampHolder[0]+1));
public T pop()
T localTop;
int stampHolder = new int[1];
do
localTop = top.get(stampHolder);
while(localTop != null && !top.compareAndSet(localTop, localTop.next, stampHolder[0], stampHolder[0]+1));
return localTop;
java algorithm concurrency
I've been toying around with a simple implementation of a lock-free Stack in Java.
edit: See fixed/working version below
Do you see any issues with this implementation?
Similar implementations in native languages seem to suffer from the ABA problem, but I'm not sure if this is a problem here; obviously no pointer handling done directly in Java, and given all I care for is the end of the stack both for pop and push, I don't see how "missing" any changes in any non-tail element of the stack would cause issues.
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
final AtomicReference<T> top = new AtomicReference<T>(null);
public void push(T item)
T localTop;
do
localTop = top.get();
item.next = localTop;
while(!top.compareAndSet(localTop, item));
public T pop()
T localTop;
do
localTop = top.get();
while(localTop != null && !top.compareAndSet(localTop, localTop.next));
return localTop;
But, here's what I don't get. I've written a simple test that launches a few threads; each one pops items from a pre-existing LockFreeStack and (later, from the same thread that popped it) pushes them back.
After its popped, I increment an atomic counter, and before pushing it back, I decrement it. So I'd always expect the counter to be 0 (right after decrementing / right before pushing back onto the stack) or 1 (right after popping & incrementing).
But, that's not what happens...
public class QueueTest
static class TestStackItem extends LockFreeStack.StackItem<TestStackItem>
final AtomicInteger usageCount = new AtomicInteger(0);
public void inc() throws Exception
int c = usageCount.incrementAndGet();
if(c != 1)
throw new Exception(String.format("Usage count is %d; expected %d", c, 1));
public void dec() throws Exception
int c = usageCount.decrementAndGet();
if(c != 0)
throw new Exception(String.format("Usage count is %d; expected %d", c, 0));
public final LockFreeStack<TestStackItem> testStack = new LockFreeStack<TestStackItem>();
public void test()
final int NUM_THREADS = 4;
for(int i = 0; i < 10; i++)
TestStackItem item = new TestStackItem();
testStack.push(item);
Thread threads = new Thread[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; i++)
threads[i] = new Thread(new TestRunner());
threads[i].setDaemon(true);
threads[i].setName("Thread"+i);
threads[i].start();
while(true)
Thread.yield();
class TestRunner implements Runnable
@Override
public void run()
try
boolean pop = false;
TestStackItem lastItem = null;
while (true)
pop = !pop;
if (pop)
TestStackItem item = testStack.pop();
item.inc();
lastItem = item;
else
lastItem.dec();
testStack.push(lastItem);
lastItem = null;
catch (Exception ex)
System.out.println("exception: " + ex.toString());
throws non-deterministic exceptions, e.g.
exception: java.lang.Exception: Usage count is 1; expected 0
exception: java.lang.Exception: Usage count is 2; expected 1
or from another run
exception: java.lang.Exception: Usage count is 2; expected 0
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 3; expected 1
exception: java.lang.Exception: Usage count is 2; expected 1
So some race-condition like issue must be going on here.
What's wrong here - is this indeed ABA-related (and if so, how exactly?) or am I missing anything else?
Thanks!
NOTE: This works, but it doesn't seem to be a great solution. It's neithe garbage-free (StampedAtomicReference creates objects internally), nor does the benefit of being lock free really seem to pay off; in my benchmarks, this wasn't really faster in a single threaded environment, and when testing with 6 threads concurrently, it fell significantly behind just putting locks around the push/pop functions
based on the solution suggested below, this was indeed an ABA problem, and this small change will circumvent that:
public class LockFreeStack<T extends LockFreeStack.StackItem<T>>
public abstract static class StackItem<SELF extends StackItem<SELF>>
volatile SELF next;
// .. data ..
private final AtomicStampedReference<T> top = new AtomicStampedReference<T>(null, 0);
public void push(T item)
int stampHolder = new int[1];
T localTop;
do
localTop = top.get(stampHolder);
item.next = localTop;
while(!top.compareAndSet(localTop, item, stampHolder[0], stampHolder[0]+1));
public T pop()
T localTop;
int stampHolder = new int[1];
do
localTop = top.get(stampHolder);
while(localTop != null && !top.compareAndSet(localTop, localTop.next, stampHolder[0], stampHolder[0]+1));
return localTop;
java algorithm concurrency
java algorithm concurrency
edited yesterday
asked yesterday
Bogey
1,5151125
1,5151125
1
@Thilo en.wikipedia.org/wiki/ABA_problem or for lock free stacks described in cs.cmu.edu/~410-s05/lectures/L31_LockFree.pdf
– Bogey
yesterday
Have you try it using sycnh section with regular LinkedList? I think you have a bug in this weird cycle with checking i % 2 == 1
– Anton
yesterday
3
Might I suggest codereview.stackexchange.com as a better site for this question?
– João Mendes
yesterday
1
@JoãoMendes Thanks - wasn't quite sure where to post best as I already know there's a bug in the implementation (or in the test), and the question is rather why instead of if ;-) But will certainly try over there in a bit if that's the better place!
– Bogey
yesterday
1
@Bogey Well, if you're looking for help with a specific bug, then here is probably better. I thought you wanted more global help. I caught the "do you see any issues" line and probably misread the rest of the question... :)
– João Mendes
yesterday
|
show 1 more comment
1
@Thilo en.wikipedia.org/wiki/ABA_problem or for lock free stacks described in cs.cmu.edu/~410-s05/lectures/L31_LockFree.pdf
– Bogey
yesterday
Have you try it using sycnh section with regular LinkedList? I think you have a bug in this weird cycle with checking i % 2 == 1
– Anton
yesterday
3
Might I suggest codereview.stackexchange.com as a better site for this question?
– João Mendes
yesterday
1
@JoãoMendes Thanks - wasn't quite sure where to post best as I already know there's a bug in the implementation (or in the test), and the question is rather why instead of if ;-) But will certainly try over there in a bit if that's the better place!
– Bogey
yesterday
1
@Bogey Well, if you're looking for help with a specific bug, then here is probably better. I thought you wanted more global help. I caught the "do you see any issues" line and probably misread the rest of the question... :)
– João Mendes
yesterday
1
1
@Thilo en.wikipedia.org/wiki/ABA_problem or for lock free stacks described in cs.cmu.edu/~410-s05/lectures/L31_LockFree.pdf
– Bogey
yesterday
@Thilo en.wikipedia.org/wiki/ABA_problem or for lock free stacks described in cs.cmu.edu/~410-s05/lectures/L31_LockFree.pdf
– Bogey
yesterday
Have you try it using sycnh section with regular LinkedList? I think you have a bug in this weird cycle with checking i % 2 == 1
– Anton
yesterday
Have you try it using sycnh section with regular LinkedList? I think you have a bug in this weird cycle with checking i % 2 == 1
– Anton
yesterday
3
3
Might I suggest codereview.stackexchange.com as a better site for this question?
– João Mendes
yesterday
Might I suggest codereview.stackexchange.com as a better site for this question?
– João Mendes
yesterday
1
1
@JoãoMendes Thanks - wasn't quite sure where to post best as I already know there's a bug in the implementation (or in the test), and the question is rather why instead of if ;-) But will certainly try over there in a bit if that's the better place!
– Bogey
yesterday
@JoãoMendes Thanks - wasn't quite sure where to post best as I already know there's a bug in the implementation (or in the test), and the question is rather why instead of if ;-) But will certainly try over there in a bit if that's the better place!
– Bogey
yesterday
1
1
@Bogey Well, if you're looking for help with a specific bug, then here is probably better. I thought you wanted more global help. I caught the "do you see any issues" line and probably misread the rest of the question... :)
– João Mendes
yesterday
@Bogey Well, if you're looking for help with a specific bug, then here is probably better. I thought you wanted more global help. I caught the "do you see any issues" line and probably misread the rest of the question... :)
– João Mendes
yesterday
|
show 1 more comment
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
You don't really need this weird cycle with "if condition" and "lastItem" in your test, you can reproduce the bug by simply pop and push same node.
To fix mentioned above issue, you can create new TestStackItem when pushing it into the stack (and passing existing counter into the new creared node) or you can use AtomicStampedReference to see if node has been modified.
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
add a comment |
up vote
1
down vote
Yes, your stack has an ABA problem.
Thread A
pop
doeslocalTop = top.get()
and readslocalTop.next
Other threads pop a bunch of stuff and put it back in a different order, but Thread A's
localTop
is still the last one pushed.Thread A's CAS succeeds, but it corrupts the stack, because the value it read from
localTop.next
is no longer accurate.
Lock free data structures are a lot easier to implement in garbage-collected languages like Java than they are in other languages, though. Your ABA problem goes away if push() allocates a new stack item every time. Then StackItem.next
can be final and the whole thing becomes a lot easier to reason about.
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
You don't really need this weird cycle with "if condition" and "lastItem" in your test, you can reproduce the bug by simply pop and push same node.
To fix mentioned above issue, you can create new TestStackItem when pushing it into the stack (and passing existing counter into the new creared node) or you can use AtomicStampedReference to see if node has been modified.
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
add a comment |
up vote
1
down vote
accepted
You don't really need this weird cycle with "if condition" and "lastItem" in your test, you can reproduce the bug by simply pop and push same node.
To fix mentioned above issue, you can create new TestStackItem when pushing it into the stack (and passing existing counter into the new creared node) or you can use AtomicStampedReference to see if node has been modified.
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
You don't really need this weird cycle with "if condition" and "lastItem" in your test, you can reproduce the bug by simply pop and push same node.
To fix mentioned above issue, you can create new TestStackItem when pushing it into the stack (and passing existing counter into the new creared node) or you can use AtomicStampedReference to see if node has been modified.
You don't really need this weird cycle with "if condition" and "lastItem" in your test, you can reproduce the bug by simply pop and push same node.
To fix mentioned above issue, you can create new TestStackItem when pushing it into the stack (and passing existing counter into the new creared node) or you can use AtomicStampedReference to see if node has been modified.
answered yesterday
Anton
3,80032238
3,80032238
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
add a comment |
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
Thanks - I'm trying to avoid producing any garbage whatsoever (its more of a theoretical exercise out of curiosity to be honest), so was actively trying to avoid creating wrapping nodes - will look into AtomicStampedReference though, that sounds promising
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
works like a charm using a stampedreference, will update the original topic for future reference
– Bogey
yesterday
add a comment |
up vote
1
down vote
Yes, your stack has an ABA problem.
Thread A
pop
doeslocalTop = top.get()
and readslocalTop.next
Other threads pop a bunch of stuff and put it back in a different order, but Thread A's
localTop
is still the last one pushed.Thread A's CAS succeeds, but it corrupts the stack, because the value it read from
localTop.next
is no longer accurate.
Lock free data structures are a lot easier to implement in garbage-collected languages like Java than they are in other languages, though. Your ABA problem goes away if push() allocates a new stack item every time. Then StackItem.next
can be final and the whole thing becomes a lot easier to reason about.
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
add a comment |
up vote
1
down vote
Yes, your stack has an ABA problem.
Thread A
pop
doeslocalTop = top.get()
and readslocalTop.next
Other threads pop a bunch of stuff and put it back in a different order, but Thread A's
localTop
is still the last one pushed.Thread A's CAS succeeds, but it corrupts the stack, because the value it read from
localTop.next
is no longer accurate.
Lock free data structures are a lot easier to implement in garbage-collected languages like Java than they are in other languages, though. Your ABA problem goes away if push() allocates a new stack item every time. Then StackItem.next
can be final and the whole thing becomes a lot easier to reason about.
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
add a comment |
up vote
1
down vote
up vote
1
down vote
Yes, your stack has an ABA problem.
Thread A
pop
doeslocalTop = top.get()
and readslocalTop.next
Other threads pop a bunch of stuff and put it back in a different order, but Thread A's
localTop
is still the last one pushed.Thread A's CAS succeeds, but it corrupts the stack, because the value it read from
localTop.next
is no longer accurate.
Lock free data structures are a lot easier to implement in garbage-collected languages like Java than they are in other languages, though. Your ABA problem goes away if push() allocates a new stack item every time. Then StackItem.next
can be final and the whole thing becomes a lot easier to reason about.
Yes, your stack has an ABA problem.
Thread A
pop
doeslocalTop = top.get()
and readslocalTop.next
Other threads pop a bunch of stuff and put it back in a different order, but Thread A's
localTop
is still the last one pushed.Thread A's CAS succeeds, but it corrupts the stack, because the value it read from
localTop.next
is no longer accurate.
Lock free data structures are a lot easier to implement in garbage-collected languages like Java than they are in other languages, though. Your ABA problem goes away if push() allocates a new stack item every time. Then StackItem.next
can be final and the whole thing becomes a lot easier to reason about.
answered yesterday
Matt Timmermans
17.4k11332
17.4k11332
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
add a comment |
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
Ahh.. My mistake was thinking that making StackItem.next volatile would mean its current value will be reflected even if changed by another exchange operation in the meantime - but not true of course as its read only once when entering the CAS loop, so could indeed be outdated by the time CAS succeeds. Thanks! That does explain it!
– Bogey
yesterday
add a comment |
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53224342%2flock-free-concurrent-stack-implementation%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
@Thilo en.wikipedia.org/wiki/ABA_problem or for lock free stacks described in cs.cmu.edu/~410-s05/lectures/L31_LockFree.pdf
– Bogey
yesterday
Have you try it using sycnh section with regular LinkedList? I think you have a bug in this weird cycle with checking i % 2 == 1
– Anton
yesterday
3
Might I suggest codereview.stackexchange.com as a better site for this question?
– João Mendes
yesterday
1
@JoãoMendes Thanks - wasn't quite sure where to post best as I already know there's a bug in the implementation (or in the test), and the question is rather why instead of if ;-) But will certainly try over there in a bit if that's the better place!
– Bogey
yesterday
1
@Bogey Well, if you're looking for help with a specific bug, then here is probably better. I thought you wanted more global help. I caught the "do you see any issues" line and probably misread the rest of the question... :)
– João Mendes
yesterday