Live locks, perhaps you need to know about

Two days ago to see geeks time  Java concurrent course of time, to a brush concept: live lock. Deadlock, but added no stranger to live lock is first heard.

Before introducing live lock, let's review the deadlock. The following example simulates a transfer service, multi-threaded environment, in order to account the amount of security, the account has been locked.

 1public class Account {
 2    public Account(int balance, String card) {
 3        this.balance = balance;
 4        this.card = card;
 5    }
 6    private int balance;
 7    private String card;
 8    public void addMoney(int amount) {
 9        balance += amount;
10    }
11      // 省略 get set 方法
12}
13public class AccountDeadLock {
14    public static void transfer(Account from, Account to, int amount) throws InterruptedException {
15        // 模拟正常的前置业务
16        TimeUnit.SECONDS.sleep(1);
17        synchronized (from) {
18            System.out.println(Thread.currentThread().getName() + " lock from account " + from.getCard());
19            synchronized (to) {
20                System.out.println(Thread.currentThread().getName() + " lock to account " + to.getCard());
21                // 转出账号扣钱
22                from.addMoney(-amount);
23                // 转入账号加钱
24                to.addMoney(amount);
25            }
26        }
27        System.out.println("transfer success");
28    }
29
30    public static void main(String[] args) {
31        Account from = new Account(100, "6000001");
32        Account to = new Account(100, "6000002");
33
34        ExecutorService threadPool = Executors.newFixedThreadPool(2);
35
36        // 线程 1
37        threadPool.execute(() -> {
38            try {
39                transfer(from, to, 50);
40            } catch (InterruptedException e) {
41                e.printStackTrace();
42            }
43        });
44
45        // 线程 2
46        threadPool.execute(() -> {
47            try {
48                transfer(to, from, 30);
49            } catch (InterruptedException e) {
50                e.printStackTrace();
51            }
52        });
53
54
55    }
56}

In the above example, when two threads into the transfer method, the thread 1 acquires account  6000001  the lock, the thread 2 locked account  6000002  lock.

Then when the thread 1 want to acquire  6,000,002  when the lock because the lock has been held by a thread 2, thread 1 will be blocked into the thread state to  BLOCKED . Similarly, thread 2 is the same state.

1pool-1-thread-1 lock from account 6000001
2pool-1-thread-2 lock from account 6000002

After the log, you can see two threads begin the transfer method, you go into a wait.

synchronizedNot obtain lock will block, waiting. In this case, we can use  ReentrantLock#tryLock(long timeout, TimeUnit unit)to transform. tryLockIf we can acquire the lock will return  true, if it can not acquire the lock will wait until the following conditions are met:

  • Within the timeout period to acquire the lock, return true

  • Did not get time to lock timeout to return false

  • Interrupt, an exception is thrown

After the transformation code is as follows:

 1public class Account {
 2    public Account(int balance, String card) {
 3        this.balance = balance;
 4        this.card = card;
 5    }
 6    private int balance;
 7    private String card;
 8    public void addMoney(int amount) {
 9        balance += amount;
10    }
11      // 省略 get set 方法
12}
13public class AccountLiveLock {
14
15    public static void transfer(Account from, Account to, int amount) throws InterruptedException {
16        // 模拟正常的前置业务
17        TimeUnit.SECONDS.sleep(1);
18        // 保证转账一定成功
19        while (true) {
20            if (from.lock.tryLock(1, TimeUnit.SECONDS)) {
21                try {
22                    System.out.println(Thread.currentThread().getName() + " lock from account " + from.getCard());
23                    if (to.lock.tryLock(1, TimeUnit.SECONDS)) {
24                        try {
25                            System.out.println(Thread.currentThread().getName() + " lock to account " + to.getCard());
26                            // 转出账号扣钱
27                            from.addMoney(-amount);
28                            // 转入账号加钱
29                            to.addMoney(amount);
30                            break;
31                        } finally {
32                            to.lock.unlock();
33                        }
34
35                    }
36                } finally {
37                    from.lock.unlock();
38                }
39            }
40        }
41        System.out.println("transfer success");
42
43    }
44
45    public static void main(String[] args) {
46        Account from = new Account(100, "A");
47        Account to = new Account(100, "B");
48
49        ExecutorService threadPool = Executors.newFixedThreadPool(2);
50
51        // 线程 1
52        threadPool.execute(() -> {
53            try {
54                transfer(from, to, 50);
55            } catch (InterruptedException e) {
56                e.printStackTrace();
57            }
58        });
59
60        // 线程 2
61        threadPool.execute(() -> {
62            try {
63                transfer(to, from, 30);
64            } catch (InterruptedException e) {
65                e.printStackTrace();
66            }
67        });
68    }
69}

The code above uses  while(true), the failure to obtain a lock, keep retrying until it succeeds. This method runs, lucky point, one can be successful, bad luck, it will as follows:

1pool-1-thread-1 lock from account 6000001
2pool-1-thread-2 lock from account 6000002
3pool-1-thread-2 lock from account 6000002
4pool-1-thread-1 lock from account 6000001
5pool-1-thread-1 lock from account 6000001
6pool-1-thread-2 lock from account 6000002

transfer The method has been run, but the final result was not successful, this is an example of a live lock.

Deadlock will cause thread is blocked, the program looks like the same into suspended animation. Like the road, man, you staring at me, I'm staring at you, waiting for each other to give way, and finally who can make life difficult.

You worry what? You look Zala?

Live lock is not the same thread repeat the same operation, but it was unsuccessful execution. Also take the example above, this time you step left, step to the right he, clever, and they run into. Then continuous cycle, finally who can make life difficult.

Source: know almost

分析死锁这个例子,两个线程获取的锁的顺序不一致,最后导致互相需要对方手中的锁。如果两个线程加锁顺序一致,所需条件就会一样,势必就不会产生死锁了。

我们以卡号大小为顺序,每次都给卡号比较大的账户先加锁,这样就可以解决死锁问题,代码修改如下:

 1// 其他代码不变    
 2public static void transfer(Account from, Account to, int amount) throws InterruptedException {
 3        // 模拟正常的前置业务
 4        TimeUnit.SECONDS.sleep(1);
 5        Account maxAccount=from;
 6        Account minAccount=to;
 7        if(Long.parseLong(from.getCard())<Long.parseLong(to.getCard())){
 8            maxAccount=to;
 9            minAccount=from;
10        }
11
12        synchronized (maxAccount) {
13            System.out.println(Thread.currentThread().getName() + " lock  account " + maxAccount.getCard());
14            synchronized (minAccount) {
15                System.out.println(Thread.currentThread().getName() + " lock  account " + minAccount.getCard());
16                // 转出账号扣钱
17                from.addMoney(-amount);
18                // 转入账号加钱
19                to.addMoney(amount);
20            }
21        }
22        System.out.println("transfer success");
23    }

对于活锁的例子,存在两个问题:

一是锁的锁超时时间都一样,导致两个线程几乎同时释放锁,重试时又同时上锁,然后陷入死循环。解决这个问题,我们可以使超时时间不一样,引入一定的随机性。

二是这里使用 while(true),实际开发中万万不能这么玩。这种情况我们需要设置最大的重试次数。

画外音:如果重试这么多次,一直不成功,但是业务却想成功。现在不成功,不要傻着一直试,先放下,记录下来,待会再重试补偿呗~

活锁的代码可以改成如下:

 1        public static final int MAX_TIME = 5;
 2    public static void transfer(Account from, Account to, int amount) throws InterruptedException {
 3        // 模拟正常的前置业务
 4        TimeUnit.SECONDS.sleep(1);
 5        // 保证转账一定成功
 6        Random random = new Random();
 7        int retryTimes = 0;
 8        boolean flag=false;
 9        while (retryTimes++ < MAX_TIME) {
10            // 等待时间随机
11            if (from.lock.tryLock(random.nextInt(1000), TimeUnit.MILLISECONDS)) {
12                try {
13                    System.out.println(Thread.currentThread().getName() + " lock from account " + from.getCard());
14                    if (to.lock.tryLock(random.nextInt(1000), TimeUnit.MILLISECONDS)) {
15                        try {
16                            System.out.println(Thread.currentThread().getName() + " lock to account " + to.getCard());
17                            // 转出账号扣钱
18                            from.addMoney(-amount);
19                            // 转入账号加钱
20                            to.addMoney(amount);
21                            flag=true;
22                            break;
23                        } finally {
24                            to.lock.unlock();
25                        }
26
27                    }
28                } finally {
29                    from.lock.unlock();
30                }
31            }
32        }
33        if(flag){
34            System.out.println("transfer success"); 
35        }else {
36            System.out.println("transfer failed");
37        }
38    }

总结

死锁是日常开发中比较容易碰到的情况,我们需要小心,注意加锁的顺序。活锁,碰到情况可能不常见,本质上我们只需要注意设置最大的重试次数,就不会永远陷入一直重试中。

参考链接

http://c.biancheng.net/view/4786.html

https://www.javazhiyin.com/43117.html

本文转载自公众号:楼下小黑哥

发布了50 篇原创文章 · 获赞 1628 · 访问量 203万+

Guess you like

Origin blog.csdn.net/zl1zl2zl3/article/details/104663696