/** * This file is part of ObjectFabric (http://objectfabric.org). * * ObjectFabric is licensed under the Apache License, Version 2.0, the terms * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html. * * Copyright ObjectFabric Inc. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ package part09; import java.util.ArrayList; import java.util.concurrent.CyclicBarrier; import org.junit.Assert; import org.objectfabric.JVMWorkspace; import org.objectfabric.Resource; import org.objectfabric.TSet; import org.objectfabric.Workspace; import part05.generated.MyClass; /** * ObjectFabric is based on a Software Transactional Memory. It is used internally, e.g. * for change tracking, but is also exposed through the atomic* methods. A STM allows * transactions to be executed on in-memory objects, which can simplify thread * coordination and exception handling. Transactions can also improve network and * persistence performance by batching operations. */ public class STM { public static void main(String[] args) { final Workspace workspace = new JVMWorkspace(); final Resource local = workspace.open(""); final MyClass object = new MyClass(local); /* * By default OF objects behave like usual objects. */ Assert.assertEquals(0, object.field()); object.field(1); Assert.assertEquals(1, object.field()); /* * In Java a transaction is specified as a Runnable. Starting a transaction takes * a snapshot of all the workspace objects. If another thread commits a change * concurrently, it is not visible to the current transaction. */ workspace.atomic(new Runnable() { @Override public void run() { /* * The current transaction only performs reads (field1) so will not be * restarted. We know this is the first run so we can assert the value. */ Assert.assertEquals(1, object.field()); /* * Update the field to 2 on a separate thread. */ runOnSeparateThread(new Runnable() { @Override public void run() { object.field(2); } }); /* * Field is still 1 for current thread. */ Assert.assertEquals(1, object.field()); } }); /* * Value is now 2 from the separate thread update. */ Assert.assertEquals(2, object.field()); /* * Updates remain private to a transaction until it commits. If transaction fails, * updates are discarded and never became visible to any other thread. */ workspace.atomic(new Runnable() { @Override public void run() { /* * Do an update. */ object.field(3); /* * Read the field from a separate thread. The value is still 2 as the * current transaction has not committed. */ runOnSeparateThread(new Runnable() { @Override public void run() { Assert.assertEquals(2, object.field()); } }); } }); /* * Transaction is now committed, new value is 3. */ Assert.assertEquals(3, object.field()); /* * If a transaction aborts, its updates are discarded. */ try { workspace.atomic(new Runnable() { @Override public void run() { /* * Do an update. */ object.field(4); /* * Throw an exception to abort transaction. */ throw new RuntimeException(); } }); } catch (RuntimeException ex) { } /* * Value is still 3. */ Assert.assertEquals(3, object.field()); /* * All ObjectFabric objects have shortcuts to their workspace atomic methods. The * transaction is still workspace-wide. */ object.atomic(new Runnable() { @Override public void run() { } }); /* * Multi-threading example: several threads update transactional objects a and b. * Using transactions, each thread is able to update multiple fields on each * object atomically. Threads do not see updates made by others while they update * variables, instead running in a consistent snapshot of memory where all fields * have the same value. */ final MyClass a = new MyClass(local), b = new MyClass(local); final TSet<String> set = new TSet<String>(local); final int threadCount = 8, writeCount = 100; ArrayList<Thread> threads = new ArrayList<Thread>(); final CyclicBarrier barrier = new CyclicBarrier(threadCount); for (int t = 0; t < threadCount; t++) { Thread thread = new Thread("Thread " + t) { @Override public void run() { try { barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } for (int i = 0; i < writeCount; i++) { workspace.atomic(new Runnable() { public void run() { // Assert fields equal despite concurrent updates Assert.assertEquals(a.field2(), a.field()); Assert.assertEquals(b.field(), a.field()); Assert.assertEquals(b.field2(), a.field()); // Increment fields a.field(a.field() + 1); a.field2(a.field2() + 1); b.field(b.field() + 1); b.field2(b.field2() + 1); // OF collections are also stable in a transaction and can // be safely iterated while modified by other threads for (String s : set) s.length(); set.add("value" + a.field()); } }); } } }; threads.add(thread); thread.start(); } for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { } } Assert.assertEquals(threadCount * writeCount, a.field()); Assert.assertEquals(threadCount * writeCount, a.field2()); Assert.assertEquals(threadCount * writeCount, b.field()); Assert.assertEquals(threadCount * writeCount, b.field2()); Assert.assertEquals(threadCount * writeCount, set.size()); System.out.println("Done!"); workspace.close(); } private static void runOnSeparateThread(final Runnable runnable) { Thread thread = new Thread(runnable); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }