Home > OS >  Synchronize on two String objects
Synchronize on two String objects

Time:12-14

I found answers only for synchronization on one String object, but not two.

It's not a real task, but an assignment. I have SomeLibrary that transfers money from one account to another. I can't access Account object to lock on it. I can only use SomeLibrary.transfer(String from, String to), which is not thread safe. I have method with account ID's as strings. I need to lock on both of these strings without deadlocks.

What I did so far is:

  1. Created new Strings with .intern method (String fr = from.intern()). But it's bad practice and I'm not allowed to use this method. But it worked.

  2. Created new String from old (String fr = new String(from)). This also worked (I didn't have deadlocks) but I have suspicions about this solution.

Is there other way to lock on both Strings?

I tried to use ConcurrentHashMap and put Strings there, but it didn't work.

May be there is a way to put Strings inside some objects, but where these objects should be created? I can create them inside transfer() but synchronization on a local variable is not good practice either.

My method is:

public void transfer(String from, String to, int amount) {
        String fr = new String(from);
        String too = new String(to);

        int fromHash = System.identityHashCode(fr);
        int toHash = System.identityHashCode(too);

        if (fromHash < toHash) {
            synchronizedTransfer(from, to, amount, fr, too);
        } else if (fromHash > toHash) {
            synchronizedTransfer(to, from, amount, too, fr);
        } else {
            synchronized (tieLock) {
                synchronizedTransfer(from, to, amount, fr, too);
            }
        }
    }

private void synchronizedTransfer(String from, String to, int amount, String fr, String too) {
        synchronized (fr) {
            synchronized (too) {
                SomeLibrary.transfer(from, to);
            }
        }
    }

CodePudding user response:

You can synchronize on both objects by using nested synchronized blocks and a data structure in which dedicated synchronization objects are stored. To prevent any deadlocks I would compare the two Strings lexicographically:

private static final Hashtable<String, Object> syncTable = new Hashtable<>();
private static final Object syncOnEquals = new Object();

    public void transfer(String from, String to, int amount) {
        Object syncFrom = syncTable.computeIfAbsent(from, s -> new Object());
        Object syncTo = syncTable.computeIfAbsent(to, s -> new Object());

        int comparingResult = from.compareTo(to);

        if (comparingResult > 0) {
            synchronized (syncFrom) {
                synchronized (syncTo) {
                    SomeLibrary.transfer(from, to);
                }
            }
        } else if (comparingResult < 0) {
            synchronized (syncTo) {
                synchronized (syncFrom) {
                    SomeLibrary.transfer(from, to);
                }
            }
        } else {
                synchronized (syncOnEquals) {
                    SomeLibrary.transfer(from, to);
                }
        }
    }

If you are not familiar with lambda expressions:

Object syncFrom = syncTable.computeIfAbsent(from, s -> new Object());

is equivalent to

Object syncFrom;
synchronized (syncTable) {
    syncFrom = syncTable.get(from);
    if (syncFrom == null) {
        syncFrom = new Object();
        syncTable.put(from, syncFrom);
    }
}

CodePudding user response:

What must be clear, is that for a transfer (from, to) all (from, ), (, from), (*, to), (to, *) transfers might need to be guarded.

The only way to do that is to synchronize of from and synchronize on to. This could cause a dead lock when there is a transfer (to, from). For that one can order from and to so that synchronisation AAA comes before synchronisation BBB.

An other approach is to have atomic deposit (from, -amount), deposit (to, amount) and do all in a transaction that can be rolled back.

Now on the synchronisation object: this must be a unique Object instance. You could put use the account ID String, as put in a Map (Set). As such you have a unique object as key in the map. Of course the map operations suffer from concurrency too, use: Collections.synchronizedMap. And you need to update it, add/remove account IDs.

As transactions are a separate topic, nest two synchronisations in canonical order.

  • Related