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;











share|improve this question



















  • 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














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;











share|improve this question



















  • 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












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;











share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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












  • 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












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.






share|improve this answer




















  • 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

















up vote
1
down vote













Yes, your stack has an ABA problem.



  • Thread A pop does localTop = top.get() and reads localTop.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.






share|improve this answer




















  • 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










Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















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






























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.






share|improve this answer




















  • 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














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.






share|improve this answer




















  • 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












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.






share|improve this answer












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.







share|improve this answer












share|improve this answer



share|improve this answer










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
















  • 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












up vote
1
down vote













Yes, your stack has an ABA problem.



  • Thread A pop does localTop = top.get() and reads localTop.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.






share|improve this answer




















  • 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














up vote
1
down vote













Yes, your stack has an ABA problem.



  • Thread A pop does localTop = top.get() and reads localTop.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.






share|improve this answer




















  • 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












up vote
1
down vote










up vote
1
down vote









Yes, your stack has an ABA problem.



  • Thread A pop does localTop = top.get() and reads localTop.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.






share|improve this answer












Yes, your stack has an ABA problem.



  • Thread A pop does localTop = top.get() and reads localTop.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.







share|improve this answer












share|improve this answer



share|improve this answer










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
















  • 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

















 

draft saved


draft discarded















































 


draft saved


draft discarded














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














































































Popular posts from this blog

Use pre created SQLite database for Android project in kotlin

Darth Vader #20

Ondo