Живые замки, возможно, вам нужно знать о

Два дня назад , чтобы увидеть вундеркинд время  Java параллельного хода времени, к концепции кисти: живой замок. Тупик, но добавил , не привыкать к живой замок не первый услышал.

Перед введением живого замка, давайте рассмотрим затор. Следующий пример имитирует передачи обслуживания, многопоточной среде, с тем чтобы учесть сумму безопасности, учетная запись была заблокирована.

 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}

В приведенном выше примере, когда два потока в способ передачи, приобретает нить 1 счет  6000001  замка, нить 2 заблокирован счет  6000002  блокировки.

Затем , когда поток 1 хочет приобрести  6,000,002 ,  когда замок , потому что замок был проведен по теме 2, резьба 1 будет заблокирован в состояние резьбы  BLOCKED . Аналогичным образом , поток 2 одно и то же состояние.

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

После того, как журнал, вы можете увидеть два потока начинает метод передачи, вы идете в засаду.

synchronizedНе получить замок заблокирует, ожидая. В этом случае мы можем использовать  ReentrantLock#tryLock(long timeout, TimeUnit unit)для преобразования. tryLockЕсли мы можем приобрести замок вернется  true, если он не может приобрести замок будет ждать , пока следующие условия не будут выполнены:

  • В период ожидания получения блокировки, возврат true

  • Не получить время тайм-аута блокировки для возврата false

  • Прерывание, генерируется исключение

После преобразования кода выглядит следующим образом:

 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}

Код выше использования  while(true), неспособность получить блокировку, чтобы попытки , пока не удается. Этот метод работает, повезло точка, один может быть успешным, невезение, это будет следующим образом :

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 Этот метод был запущен, но конечный результат не был успешным, это пример живого замка.

Тупик вызовет поток блокируется, программа выглядит как то же самое в анабиоз. Как дороге, человек, ты смотришь на меня, я смотрел на тебя, ожидая друг друга, чтобы уступить дорогу, и, наконец, кто может сделать жизнь трудной.

Вы беспокоитесь, что? Вы смотрите Zala?

Живой замок не тот же поток повторите ту же операцию, но она не увенчалась успехом исполнение. Также рассмотрим пример выше, на этот раз вы шаг влево, шаг вправо он, умный, и они бегут в. Затем непрерывный цикл, в конце концов, кто может сделать жизнь трудной.

Источник: знаю почти

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

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

 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万+

рекомендация

отblog.csdn.net/zl1zl2zl3/article/details/104663696
рекомендация