Java avoids deadlock through the order of locks

Example

For bank account transfer, if two users transfer money, if they use the general synchronized nesting, it is easy to cause deadlock. Now we use the solution similar to the philosopher's problem: first obtain the same lock, then we can get the next one. The judgment is based on the return value of hashcode () generated from System.identityHashCode () as the unique identification. If the same, we add another lock.

// Deadlock version
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public void debit(int amount) {
        System.out.println("after debit " + amount + " " + this.money + " -> " + (this.money-amount));
        this.money -= amount;
    }

    public void credit(int amount) {
        System.out.println("after credit " + amount + " " + this.money + " -> " + (this.money+amount));
        this.money += amount;
    }

    public int get() {
        return this.money;
    }
}

public class OrderLock {
    private static final Object tieLock = new Object();

    public void transferMoney(final Account fromAcct, final Account toAcct, final int amount) 
            throws InsufficientResourcesException {
        class Helper {
            public void transfer() throws InsufficientResourcesException {
                if (fromAcct.get() < amount) 
                    throw new InsufficientResourcesException();
                else {
                    fromAcct.debit(amount);
                    toAcct.credit(amount);
                }
            }
        }

        // When two users use these two accounts to transfer money to the other party, they are deadlocked; because one party's fromAcct account is the other party's toAcct account
        synchronized (fromAcct) {
            synchronized (toAcct) {
                new Helper().transfer();
            }
        }
    }

    class MyThread implements Runnable {
        private Account fromAcct;
        private Account toAcct;
        private int amount;

        public MyThread(Account fromAcct, Account toAcct, int amount) {
            this.fromAcct = fromAcct;
            this.toAcct = toAcct;
            this.amount = amount;
        }


        @Override
        public void run() {
            try {
                transferMoney(this.fromAcct, this.toAcct, this.amount);
            } catch (InsufficientResourcesException e) {
                System.out.println("operation failed");
            }
        }

    }

    public static void main(String[] args) {
        Account fromAcct = new Account(100);
        Account toAcct = new Account(230);
        OrderLock orderLock = new OrderLock();
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            if ((i & 1) == 0)
                threadPool.execute(orderLock.new MyThread(fromAcct, toAcct, 10));
            else threadPool.execute(orderLock.new MyThread(toAcct, fromAcct, 10));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
// Resolve deadlock version
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public void debit(int amount) {
        System.out.println("after debit " + amount + " " + this.money + " -> " + (this.money-amount));
        this.money -= amount;
    }

    public void credit(int amount) {
        System.out.println("after credit " + amount + " " + this.money + " -> " + (this.money+amount));
        this.money += amount;
    }

    public int get() {
        return this.money;
    }
}

public class OrderLock {
    private static final Object tieLock = new Object();

    public void transferMoney(final Account fromAcct, final Account toAcct, final int amount) 
            throws InsufficientResourcesException {
        class Helper {
            public void transfer() throws InsufficientResourcesException {
                if (fromAcct.get() < amount) 
                    throw new InsufficientResourcesException();
                else {
                    fromAcct.debit(amount);
                    toAcct.credit(amount);
                }
            }
        }

        // Both sides of the transfer share the objects of these two accounts. Otherwise, the following locking order cannot be sorted in the following way
        int fromHash = System.identityHashCode(fromAcct);
        int toHash = System.identityHashCode(toAcct);

        if (fromHash < toHash) {
            synchronized (fromAcct) {
                synchronized (toAcct) {
                    new Helper().transfer();
                }
            }
        } else if (fromHash > toHash) {
            synchronized (toAcct) {
                synchronized (fromAcct) {
                    new Helper().transfer();
                }   
            }
        } else {
            synchronized (tieLock) {
                synchronized (fromAcct) {
                    synchronized (toAcct) {
                        new Helper().transfer();
                    }
                }
            }
        }
    }

    class MyThread implements Runnable {
        private Account fromAcct;
        private Account toAcct;
        private int amount;

        public MyThread(Account fromAcct, Account toAcct, int amount) {
            this.fromAcct = fromAcct;
            this.toAcct = toAcct;
            this.amount = amount;
        }


        @Override
        public void run() {
            try {
                transferMoney(this.fromAcct, this.toAcct, this.amount);
            } catch (InsufficientResourcesException e) {
                System.out.println("operation failed");
            }
        }

    }

    public static void main(String[] args) {
        // Both parties share these two account objects
        Account fromAcct = new Account(100);
        Account toAcct = new Account(230);
        OrderLock orderLock = new OrderLock();
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            if ((i & 1) == 0)
                threadPool.execute(orderLock.new MyThread(fromAcct, toAcct, 10));
            // Note: the transferred account becomes toAcct, and the transferred account becomes fromAcct
            else threadPool.execute(orderLock.new MyThread(toAcct, fromAcct, 10));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

Doubts about the above code

1 nested synchronized block

    synchronized (fromAcct) {
            synchronized (toAcct) {
                new Helper().transfer();
            }   
    }
  • 1
  • 2
  • 3
  • 4
  • 5

Understanding: nested synchronized is to obtain locks in the order of nesting, that is, first obtain the outermost lock fromAcct, and then obtain the locks of toAcct.

2 lock from acct and toAcct

Note: the account of transfer double issue is reversed, Jack's toAcct becomes Bob's fromAcct

Four necessary conditions of deadlock

  1. Exclusive use (resource exclusive)
    A resource can only be used by one process at a time

  2. Inalienable (Inalienable)
    Resource applicants cannot forcibly seize resources from resource holders, and resources can only be released voluntarily by the holders

  3. Request and hold (partial distribution, possession application)
    A process keeps the possession of original resources while applying for new resources (as long as this is dynamic application and dynamic allocation)

  4. Circular wait
    There is a process waiting queue {P1, P2 , Pn}, where P1 waits for the resource occupied by P2, P2 waits for the resource occupied by P3 , Pn waits for the resources occupied by P1, and the stars give a process a waiting loop

hashCode and identityHashCode

  • The hashCode() method is a method under the Object class for the inheritance class to rewrite. The hash value is calculated according to the memory address of the Object. The String class rewrites the hashCode method and calculates the hash value according to the character sequence instead.

  • The identityHashCode() method is a static method in the System class, which calculates the hash value according to the memory address of the object

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println("s1 Of hashCode Value::" + s1.hashCode());
        System.out.println("s2 Of hashCode Value::" + s2.hashCode());
        System.out.println("s1 Of identityHashCode Value::" + System.identityHashCode(s1));
        System.out.println("s2 Of identityHashCode Value::" + System.identityHashCode(s2));
        System.out.println("--------------------------");
        String s3 = "Java";
        String s4 = "Java";
        System.out.println("s3 Of hashCode Value::" + s3.hashCode());
        System.out.println("s4 Of hashCode Value::" + s4.hashCode());
        System.out.println("s3 Of identityHashCode Value::" + System.identityHashCode(s3));
        System.out.println("s4 Of identityHashCode Value::" + System.identityHashCode(s4));
    }

// print
s1 Of hashCode Value::99162322
s2 Of hashCode Value::99162322
s1 Of identityHashCode Value::817899724
s2 Of identityHashCode Value::397836821
--------------------------
s3 Of hashCode Value::2301506
s4 Of hashCode Value::2301506
s3 Of identityHashCode Value::1326857436
s4 Of identityHashCode Value::1326857436
Published 29 original articles, won praise 0, visited 755
Private letter follow

Tags: Java

Posted on Mon, 16 Mar 2020 01:14:44 -0400 by kanika