二日前、オタクの時間を確認し 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}
上記の例では、転写方法に2つのスレッドが、スレッド1を取得するアカウントとき 6000001 ロック、スレッド2ロックされたアカウント 6000002 ロック。
取得し、次いで場合、スレッド1希望 6000002 ロックがスレッド2に保持されているので、ロックは、スレッド1とスレッド状態にブロックされる BLOCKED。同様に、スレッド2は、同じ状態です。
1pool-1-thread-1 lock from account 6000001
2pool-1-thread-2 lock from account 6000002
ログインした後、あなたは待ちに入り、2つのスレッドが転送方法を始めるのを見ることができます。
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)
は、ロックを取得する失敗は、それが成功するまで再試行保ちます。このメソッドを実行すると、ラッキーポイントは、1が成功し、不運をすることができ、それは次の通りになります。
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
メソッドが実行されているが、最終的な結果が成功しなかった、これはライブロックの一例です。
デッドロックが仮死に、スレッドがブロックされているのと同じようなプログラムのルックスを引き起こします。道路と同じように、男は、あなたが私を見つめ、私はお互いに道を譲るのを待って、あなたを見つめてる、そして最終的に誰が生活が困難になることができます。
あなたは何を心配しますか?あなたはザラに見えますか?
ライブロックは、同じスレッドリピート同じ操作ではありませんが、それは失敗し、実行しました。また、上記のあなたは、右のステップ彼は、賢い左ステップ、この時間を例に取ると、彼らはに実行します。その後、連続サイクルは、最終的に誰が生活が困難になることができます。
出典:ほとんど知っています
分析死锁这个例子,两个线程获取的锁的顺序不一致,最后导致互相需要对方手中的锁。如果两个线程加锁顺序一致,所需条件就会一样,势必就不会产生死锁了。
我们以卡号大小为顺序,每次都给卡号比较大的账户先加锁,这样就可以解决死锁问题,代码修改如下:
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
本文转载自公众号:楼下小黑哥