
前两天刷到一个概念:活锁。死锁,倒是不陌生,活锁却是第一次听到。
在介绍活锁之前,我们先来复习一下死锁。下面的例子模拟一个转账业务,多线程环境,为了账户金额安全,对账户进行了加锁。
...
上述例子中,当两个线程进入转账方法,线程 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
通过日志,可以看到两个线程开始转账方法之后,就陷入等待。
synchronized获取不到锁就会阻塞,进行等待。既然这样,我们可以使用ReentrantLock#tryLock(long timeout, TimeUnit unit)进行改造。tryLock若能获取锁,将会返回 true,若不能获取锁将会进行等待,直到满足下列条件:
改造后代码如下:
...
...
上面代码使用了 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 方法一直在运行,但是最终却得不到成功结果,这就是个活锁的例子。
死锁将会造成线程阻塞,程序看起来就像陷入假死一样。就像路上碰到人,你盯着我,我盯着你,互相等待对方让道,最后谁也过不去。
...
你愁啥?瞅你咋啦?
而活锁不一样,线程不断重复同样的操作,但也却执行不成功。还拿上面举例,这次你往左一步,他往右边一步,巧了,又碰上。然后不断循环,最后还是谁也过不去。
...
分析死锁这个例子,两个线程获取的锁的顺序不一致,最后导致互相需要对方手中的锁。如果两个线程加锁顺序一致,所需条件就会一样,势必就不会产生死锁了。
我们以卡号大小为顺序,每次都给卡号比较大的账户先加锁,这样就可以解决死锁问题,代码修改如下:
...对于活锁的例子,存在两个问题:
一是锁的锁超时时间都一样,导致两个线程几乎同时释放锁,重试时又同时上锁,然后陷入死循环。解决这个问题,我们可以使超时时间不一样,引入一定的随机性。
二是这里使用 while(true),实际开发中万万不能这么玩。这种情况我们需要设置最大的重试次数。
画外音:如果重试这么多次,一直不成功,但是业务却想成功。现在不成功,不要傻着一直试,先放下,记录下来,待会再重试补偿呗~
活锁的代码可以改成如下:
...总结
死锁是日常开发中比较容易碰到的情况,我们需要小心,注意加锁的顺序。活锁,碰到情况可能不常见,本质上我们只需要注意设置最大的重试次数,就不会永远陷入一直重试中。
今天,给大家整理汇总了:2020年互联网大厂真实面试题,主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合。
...