0

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.

Nooblhu
  • 552
  • 15
  • 33

2 Answers2

1

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

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

Avneet Paul
  • 293
  • 1
  • 7
  • I believe this is is the intention from the helper methods, however I'm still gonna try to verify the difference in performance. – Nooblhu May 01 '17 at 23:39
  • Seems like the issue was that my alternative solutions didn't use any order in locks, while the helper methods were way to complex for just 2 objects (create an array of the object, sort it and then iterate it). I've been searching for an answer focused in the object isolation, not know about the importance of the order. Great Lesson from this classic book, thanks for the guiding to this specific concept. – Nooblhu May 02 '17 at 00:11
  • 1
    Friend, you must read this book unless you haven't already. If you read it you'll be so happy. It'll give you immense knowledge on a complex topic that's difficult to get otherwise. It also talks about using 'open calls' to avoid deadlocks. – Avneet Paul May 03 '17 at 04:07
0

You can use Taooka distributed lock manager with some SQL-database to write code like this:

if taooka.Lock(srcUser.ID, time.Minute) {

    if srcUser.Balance < transferSum {
        taooka.Unlock(srcUser.ID)
        return "Not enough money"
    }

    if taooka.Lock(dstUser.ID, time.Minute) {

        srcSQLServer = GetSQLServerFor(srcUser)
        dstSQLServer = GetSQLServerFor(dstUser)

        transferID = journalSQLServer.do("INSERT INTO money_transfer SET start_time=NOW()")

        srcSQLServer.do("INSERT INTO money_log SET transfer_id=", transferID, ", user_id=", srcUser.ID, ", sum=", -transferSum)
        dstSQLServer.do("INSERT INTO money_log SET transfer_id=", transferID, ", user_id=", dstUser.ID, ", sum=", transferSum)

        journalSQLServer.do("DELETE FROM money_transfer WHERE id=", transferID)

        taooka.Unlock(dstUser.ID)
        taooka.Unlock(srcUser.ID)

        return "Ok"
    }

    taooka.Unlock(srcUser.ID)
    return "wait please..."

}
else {
    return "wait please..."
}

and run in background this code:

for {
    partialTransferID = journalSQLServer.do("SELECT id FROM money_transfer WHERE start_time < DATE_SUB(NOW(), INTERVAL 1 MINUTE) LIMIT 1")

    if partialTransferID > 0 {
        foreach sql in SQLServers {
            sql.do("DELETE FROM money_log WHERE transfer_id=", partialTransferID)
        }
        journalSQLServer.do("DELETE FROM money_transfer WHERE id=", partialTransferID)
        continue
    }

    Sleep(time.Second)
}

But unfortunately Taooka still hasn`t Java bindings.

Brandon S
  • 1
  • 2