diff --git a/Java_4sem.iml b/Java_4sem.iml new file mode 100644 index 0000000..22f739f --- /dev/null +++ b/Java_4sem.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2319589 --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + 4.0.0 + + ru.spbau.mit + assignments + 1.0-SNAPSHOT + jar + + assignments + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-all + 1.3 + + + junit-addons + junit-addons + 1.4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.7 + 1.7 + + -Xlint:all + + + + + org.jacoco + jacoco-maven-plugin + 0.7.5.201505241946 + + + default-prepare-agent + + prepare-agent + + + + deafult-report + test + + report + + + + deafult-check + + check + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/spbau/mit/FactoryFromSupplier.java b/src/main/java/ru/spbau/mit/FactoryFromSupplier.java new file mode 100644 index 0000000..a8a704a --- /dev/null +++ b/src/main/java/ru/spbau/mit/FactoryFromSupplier.java @@ -0,0 +1,7 @@ +package ru.spbau.mit; + +import java.util.function.Supplier; + +public interface FactoryFromSupplier { + Lazy createLazy(Supplier supplier); +} diff --git a/src/main/java/ru/spbau/mit/Lazy.java b/src/main/java/ru/spbau/mit/Lazy.java new file mode 100644 index 0000000..66428c4 --- /dev/null +++ b/src/main/java/ru/spbau/mit/Lazy.java @@ -0,0 +1,5 @@ +package ru.spbau.mit; + +public interface Lazy { + T get(); +} \ No newline at end of file diff --git a/src/main/java/ru/spbau/mit/LazyFactory.java b/src/main/java/ru/spbau/mit/LazyFactory.java new file mode 100644 index 0000000..73a93cb --- /dev/null +++ b/src/main/java/ru/spbau/mit/LazyFactory.java @@ -0,0 +1,66 @@ +package ru.spbau.mit; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; + +public class LazyFactory { + public static Lazy createLazyOneThread(final Supplier supplier) { + return new Lazy() { + private boolean isCalled = false; + private T result; + + public T get() { + if (!isCalled) { + result = supplier.get(); + isCalled = true; + } + return result; + } + }; + } + + public static Lazy createLazyMultiThread(final Supplier supplier) { + return new Lazy() { + private volatile boolean isCalled = false; + private volatile T result; + + public T get() { + if (!isCalled) { + synchronized (this) { + if (!isCalled) { + result = supplier.get(); + isCalled = true; + } + } + } + return result; + } + }; + } + + private static class LockFreeLazy implements Lazy { + private volatile T result; + private volatile Supplier supplier; + private static final AtomicReferenceFieldUpdater updater = + AtomicReferenceFieldUpdater.newUpdater(LockFreeLazy.class, Object.class, "result"); + + public LockFreeLazy(Supplier supp) { + supplier = supp; + } + + + public T get() { + Supplier supp = supplier; + if (supp != null) { + if (updater.compareAndSet(this, null, supp.get())) { + supplier = null; + } + } + return result; + } + } + + public static Lazy createLazyMultiThreadLockFree(Supplier supplier) { + return new LockFreeLazy(supplier); + } +} diff --git a/src/test/java/ru/spbau/mit/LazyFactoryTest.java b/src/test/java/ru/spbau/mit/LazyFactoryTest.java new file mode 100644 index 0000000..e4f76c9 --- /dev/null +++ b/src/test/java/ru/spbau/mit/LazyFactoryTest.java @@ -0,0 +1,195 @@ +package ru.spbau.mit; + +import org.junit.Test; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import static org.junit.Assert.*; + +public class LazyFactoryTest { + + private static final int NUMBER_OF_THREADS = 50; + + //Create suppliers + + private final Supplier nullSupplier = new Supplier() { + private boolean isCalled = false; + + @Override + public String get() { + assertFalse(isCalled); + isCalled = true; + return null; + } + }; + + private final Supplier supplier = new Supplier() { + private boolean isCalled = false; + + @Override + public String get() { + assertFalse(isCalled); + isCalled = true; + return "I've done!"; + } + }; + + //Special functions for tests + + private void checkForNullTests(Lazy lazy) { + assertNull(lazy.get()); + assertNull(lazy.get()); + } + + private void checkForEqualTests(Lazy lazy) { + String firstGet = lazy.get(); + String secondGet = lazy.get(); + assertEquals(firstGet, secondGet); + assertEquals(firstGet, "I've done!"); + } + + private Supplier createMultiThreadSupplier(final Random random) { + return new Supplier() { + @Override + public Integer get() { + return random.nextInt(500); + } + }; + } + + private static class SupplCounter implements Supplier { + private AtomicInteger cnt = new AtomicInteger(0); + private Object result; + + SupplCounter(Object result) { + this.result = result; + } + + @Override + public Object get() { + Thread.yield(); + cnt.incrementAndGet(); + return result; + } + + public Integer getCount() { + return cnt.get(); + } + } + + private void checkForMultiThread(FactoryFromSupplier factory, boolean isSupplierCalledOnce, + Object returnValue) { + SupplCounter supplier = new SupplCounter(returnValue); + Lazy lazy = factory.createLazy(supplier); + + List tasks = new ArrayList<>(); + List results = Collections.synchronizedList(new ArrayList<>()); + CyclicBarrier barrier = new CyclicBarrier(NUMBER_OF_THREADS); + + for (int i = 0; i < NUMBER_OF_THREADS; i++) { + Thread task = new Thread(() -> { + try { + barrier.await(); + } catch (Exception e) { + e.printStackTrace(); + } + results.add(lazy.get()); + }); + tasks.add(task); + task.start(); + } + + for (Thread task : tasks) { + try { + task.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + assertEquals(NUMBER_OF_THREADS, results.size()); + + for (Object result : results) { + assertTrue(returnValue == result); + } + + assertTrue(supplier.getCount() > 0); + if (isSupplierCalledOnce) { + assertTrue(1 == supplier.getCount()); + } + } + + //Tests for one thread + @Test + public void nullTestLazyForOneThread() { + Lazy lazy = LazyFactory.createLazyOneThread(nullSupplier); + checkForNullTests(lazy); + } + + @Test + public void equalsTestLazyForOneThread() { + Lazy lazy = LazyFactory.createLazyOneThread(supplier); + checkForEqualTests(lazy); + } + + private static class classWrapper { + public volatile T content; + + public classWrapper(T content) { + this.content = content; + } + } + + @Test + public void lazinessTestLazyForOneThread() { + classWrapper isAsked = new classWrapper<>(false); + + Lazy lazy = LazyFactory.createLazyOneThread(() -> { + assertTrue(isAsked.content); + return "It's OK!"; + }); + + isAsked.content = true; + } + + //MultiThread tests + + @Test + public void nullTestLazyMultiThread() { + Lazy lazy = LazyFactory.createLazyMultiThread(nullSupplier); + checkForNullTests(lazy); + } + + @Test + public void equalsTestLazyMultiThread() { + Lazy lazy = LazyFactory.createLazyMultiThread(supplier); + checkForEqualTests(lazy); + } + + @Test + public void dataRaceTestLazyMultiThread() { + checkForMultiThread(LazyFactory::createLazyMultiThread, true, new Object()); + } + + //MultiThread lock-free tests + + @Test + public void nullTestLazyMultiThreadLockFree() { + Lazy lazy = LazyFactory.createLazyMultiThreadLockFree(nullSupplier); + checkForNullTests(lazy); + } + + @Test + public void equalsTestLazyMultiThreadLockFree() { + Lazy myLazy = LazyFactory.createLazyMultiThreadLockFree(supplier); + checkForEqualTests(myLazy); + } + + @Test + public void dataRaceTestLazyMultiThreadLockFree() { + checkForMultiThread(LazyFactory::createLazyMultiThreadLockFree, false, new Object()); + } +}