diff --git a/csc/2019/2.LockFreeSet/ChausovAG/pom.xml b/csc/2019/2.LockFreeSet/ChausovAG/pom.xml new file mode 100644 index 000000000..1012416da --- /dev/null +++ b/csc/2019/2.LockFreeSet/ChausovAG/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + + dxahtepb + lock-free-set + 1.0-SNAPSHOT + + + 1.8 + 1.8 + + + + + bintray--maven + bintray + https://dl.bintray.com/devexperts/Maven + + + + + + junit + junit + 4.12 + test + + + com.devexperts.lincheck + lincheck + 2.0 + test + + + \ No newline at end of file diff --git a/csc/2019/2.LockFreeSet/ChausovAG/src/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSet.java b/csc/2019/2.LockFreeSet/ChausovAG/src/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSet.java new file mode 100644 index 000000000..0c19fc8b0 --- /dev/null +++ b/csc/2019/2.LockFreeSet/ChausovAG/src/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSet.java @@ -0,0 +1,56 @@ +package ru.cscenter.dxahtepb.lockfreeset; + +/** + * Lock-Free множество. + * @param Тип ключей + */ +public interface LockFreeSet> { + /** + * Добавить ключ к множеству + * + * Алгоритм должен быть как минимум lock-free + * + * @param value значение ключа + * @return false если value уже существует в множестве, true если элемент был добавлен + */ + boolean add(T value); + + /** + * Удалить ключ из множества + * + * Алгоритм должен быть как минимум lock-free + * + * @param value значение ключа + * @return false если ключ не был найден, true если ключ успешно удален + */ + boolean remove(T value); + + /** + * Проверка наличия ключа в множестве + * + * Алгоритм должен быть как минимум wait-free + * + * @param value значение ключа + * @return true если элемент содержится в множестве, иначе - false + */ + boolean contains(T value); + + /** + * Проверка множества на пустоту + * + * Алгоритм должен быть как минимум wait-free + * + * @return true если множество пусто, иначе - false + */ + boolean isEmpty(); + + /** + * Возвращает lock-free итератор для множества + * + * Итератор должен базироваться на концепции снапшота, см. + * @see Lock-Free Data-Structure Iterators + * + * @return новый экземпляр итератор для множества + */ + java.util.Iterator iterator(); +} \ No newline at end of file diff --git a/csc/2019/2.LockFreeSet/ChausovAG/src/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImpl.java b/csc/2019/2.LockFreeSet/ChausovAG/src/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImpl.java new file mode 100644 index 000000000..611925476 --- /dev/null +++ b/csc/2019/2.LockFreeSet/ChausovAG/src/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImpl.java @@ -0,0 +1,129 @@ +package ru.cscenter.dxahtepb.lockfreeset; + +import java.util.concurrent.atomic.AtomicMarkableReference; + +public class LockFreeSetImpl> implements LockFreeSet { + private final Node head = new Node(null, null); + + @Override + public boolean add(final T value) { + while (true) { + final Pair pair = find(value); + final Node prev = pair.prevNode; + final Node curr = pair.nextNode; + + if (curr == null || curr.value.compareTo(value) != 0) { + final Node node = new Node(value, curr); + if (prev.next.compareAndSet(curr, node, false, false)) { + return true; + } + } else { + return false; + } + } + } + + @Override + public boolean remove(final T value) { + while (true) { + final Pair pair = find(value); + final Node prev = pair.prevNode; + final Node curr = pair.nextNode; + + if (curr == null || curr.value.compareTo(value) != 0) { + return false; + } else { + final Node succ = curr.next.getReference(); + if (!curr.next.compareAndSet(succ, succ, false, true)) { + continue; + } + prev.next.compareAndSet(curr, succ, false, false); + return true; + } + } + + } + + @Override + public boolean contains(final T value) { + final boolean[] holder = new boolean[1]; + Node curr = head.next.get(holder); + while (curr != null && curr.value.compareTo(value) < 0) { + curr = curr.next.get(holder); + } + final boolean isCurrMarked = holder[0]; + return curr != null && curr.value.compareTo(value) == 0 && !isCurrMarked; + } + + private Pair find(final T value) { + retry: + while (true) { + Node prev = head; + Node curr = prev.next.getReference(); + + while (true) { + if (curr == null) { + return new Pair(prev, null); + } + + final boolean[] holder = new boolean[1]; + final Node succ = curr.next.get(holder); + final boolean isCurrMarked = holder[0]; + + if (isCurrMarked && !prev.next.compareAndSet(curr, succ, false, false)) { + continue retry; + } else { + if (!isCurrMarked && value.compareTo(curr.value) <= 0) { + return new Pair(prev, curr); + } + prev = curr; + curr = succ; + } + } + } + } + + @Override + public boolean isEmpty() { + return head.next.getReference() == null; + } + + @Override + public java.util.Iterator iterator() { + return null; + } + + private class Node { + final T value; + final AtomicMarkableReference next; + + Node(final T value, final Node next) { + this.value = value; + this.next = new AtomicMarkableReference<>(next, false); + } + + @Override + public String toString() { + return "Node(" + (value != null ? value.toString() : "null") + ")"; + } + } + + private class Pair { + final Node prevNode; + final Node nextNode; + + Pair(final Node prevNode, final Node nextNode) { + this.prevNode = prevNode; + this.nextNode = nextNode; + } + + @Override + public String toString() { + return "Pair(" + + (prevNode != null ? prevNode.toString() : "null") + + ", " + + (nextNode != null ? nextNode.toString() : "null") + + ")"; + } + } +} diff --git a/csc/2019/2.LockFreeSet/ChausovAG/test/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImplLincheck.java b/csc/2019/2.LockFreeSet/ChausovAG/test/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImplLincheck.java new file mode 100644 index 000000000..53f8a569a --- /dev/null +++ b/csc/2019/2.LockFreeSet/ChausovAG/test/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImplLincheck.java @@ -0,0 +1,45 @@ +package ru.cscenter.dxahtepb.lockfreeset; + +import com.devexperts.dxlab.lincheck.LinChecker; +import com.devexperts.dxlab.lincheck.annotations.Operation; +import com.devexperts.dxlab.lincheck.annotations.Param; +import com.devexperts.dxlab.lincheck.strategy.stress.StressCTest; +import com.devexperts.dxlab.lincheck.strategy.stress.StressOptions; +import com.devexperts.dxlab.lincheck.verifier.linearizability.LinearizabilityVerifier; +import org.junit.Test; + +@StressCTest(threads = 12) +public class LockFreeSetImplLincheck { + + private LockFreeSetImpl lockFreeSet = new LockFreeSetImpl<>(); + + @Operation + public boolean add(@Param(name = "x") Integer x) { + return lockFreeSet.add(x); + } + + @Operation + public boolean remove(@Param(name = "x") Integer x) { + return lockFreeSet.remove(x); + } + + @Operation + public boolean contains(@Param(name = "x") Integer x) { + return lockFreeSet.contains(x); + } + + @Operation + public boolean isEmpty(@Param(name = "x") Integer x) { + return lockFreeSet.contains(x); + } + + @Test + public void runTest() { + LinChecker.check(LockFreeSetImpl.class, new StressOptions() + .iterations(20000) + .threads(12) + .actorsPerThread(100) + .invocationsPerIteration(200) + .verifier(LinearizabilityVerifier.class)); + } +} diff --git a/csc/2019/2.LockFreeSet/ChausovAG/test/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImplTest.java b/csc/2019/2.LockFreeSet/ChausovAG/test/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImplTest.java new file mode 100644 index 000000000..42e289f6b --- /dev/null +++ b/csc/2019/2.LockFreeSet/ChausovAG/test/main/java/ru/cscenter/dxahtepb/lockfreeset/LockFreeSetImplTest.java @@ -0,0 +1,46 @@ +package ru.cscenter.dxahtepb.lockfreeset; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LockFreeSetImplTest { + + @Test + public void testEmptyByDefault() { + LockFreeSetImpl set = new LockFreeSetImpl<>(); + assertTrue(set.isEmpty()); + } + + @Test + public void testBasicOperations() { + LockFreeSetImpl set = new LockFreeSetImpl<>(); + + assertFalse(set.contains(1)); + + assertTrue(set.add(1)); + assertTrue(set.add(2)); + assertTrue(set.add(4)); + assertTrue(set.add(3)); + assertFalse(set.add(1)); + assertFalse(set.add(3)); + + assertTrue(set.contains(1)); + assertTrue(set.contains(2)); + assertTrue(set.contains(3)); + assertTrue(set.contains(4)); + + assertTrue(set.remove(1)); + assertFalse(set.contains(1)); + + assertFalse(set.remove(20)); + + assertFalse(set.isEmpty()); + + assertTrue(set.remove(2)); + assertTrue(set.remove(3)); + assertTrue(set.remove(4)); + assertTrue(set.isEmpty()); + } +}