Search code examples
javaconcurrencylockingtransaction-isolation

How do I achieve a performant object isolation solution in concurrent transactions


I'm creating this method issueTransfer to achieve a transfer from one account to another.

My solution is:

public void issueTransfer(final int amount, final Account src,
        final Account dst) {
    /*
     * TODO implement issueTransfer using object-based isolation instead of
     * global isolation, based on the reference code provided in
     * BankTransactionsUsingGlobalIsolation. Keep in mind that isolation
     * must be applied to both src and dst.
     */

    isolated(src, dst, () -> {
        if (src.withdraw(amount)) {
            dst.deposit(amount);
        }
    });
}

The solution for global isolation is:

public void issueTransfer(final int amount, final Account src,
        final Account dst) {
    isolated(() -> {
        src.performTransfer(amount, dst);
    });
}

The global and object isolated methods applied are defined like this:

public static void isolated(Runnable runnable) {
    isolatedManager.acquireAllLocks();

    try {
        runnable.run();
    } finally {
        isolatedManager.releaseAllLocks();
    }
}

public static void isolated(Object obj1, Object obj2, Runnable runnable) {
    Object[] objArr = new Object[]{obj1, obj2};
    isolatedManager.acquireLocksFor(objArr);

    try {
        runnable.run();
    } finally {
        isolatedManager.releaseLocksFor(objArr);
    }
}

Helper methods (acquire and release) are:

public void acquireAllLocks() {
    for(int i = 0; i < this.locks.length; ++i) {
        this.locks[i].lock();
    }
}

public void releaseAllLocks() {
    for(int i = this.locks.length - 1; i >= 0; --i) {
        this.locks[i].unlock();
    }
}

public void acquireLocksFor(Object[] objects) {
    TreeSet<Object> sorted = this.createSortedObjects(objects);
    Iterator var3 = sorted.iterator();

    while(var3.hasNext()) {
        Object obj = var3.next();
        int lockIndex = this.lockIndexFor(obj);
        this.locks[lockIndex].lock();
    }
}

public void releaseLocksFor(Object[] objects) {
    TreeSet<Object> sorted = this.createSortedObjects(objects);
    Iterator var3 = sorted.iterator();

    while(var3.hasNext()) {
        Object obj = var3.next();
        int lockIndex = this.lockIndexFor(obj);
        this.locks[lockIndex].unlock();
    }
}

private int lockIndexFor(Object obj) {
    return Math.abs(obj.hashCode()) % 64;
}

private TreeSet<Object> createSortedObjects(Object[] objects) {
    TreeSet<Object> sorted = new TreeSet(new Comparator<Object>() {
        public int compare(Object o1, Object o2) {
            return IsolatedManager.this.lockIndexFor(o1) - IsolatedManager.this.lockIndexFor(o2);
        }
    });
    Object[] var3 = objects;
    int var4 = objects.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        Object obj = var3[var5];
        sorted.add(obj);
    }

    return sorted;
}

As you can see, I'm supposedly applying the 2nd method (object isolation) as required by the documentation. The test is passing without problems:

public void testObjectIsolation() {
    testDriver(new BankTransactionsUsingGlobalIsolation());
    final long globalTime = testDriver(
            new BankTransactionsUsingGlobalIsolation());

    testDriver(new BankTransactionsUsingObjectIsolation());
    final long objectTime = testDriver(
            new BankTransactionsUsingObjectIsolation());
    final double improvement = (double)globalTime / (double)objectTime;

    final int ncores = getNCores();
    final double expected = (double)ncores * 0.75;
    final String msg = String.format("Expected an improvement of at " +
            "least %fx with object-based isolation, but saw %fx", expected,
            improvement);
    assertTrue(msg, improvement >= expected);
}

However the platform used for evaluation says I'm not passing the test either with 2 or 4 cores. Depending on when I do it, sometimes I pass 1 of the tests (I assume, the 2 cores test.

As you can see from the test that I pass, my object isolation solution is faster (in a 1:0.75 ratio per core) than my global isolation. Is it a platform failure or can my code be improved? I've tried using lock, unlock, and trylock, but my solution seems to work faster but not enough yet.


Solution

  • You could try this approach by Brian Goetz and Tim Pierels from the book, "Java Concurrency in Practice"

    http://jcip.net/listings/InduceLockOrder.java