更新概念
所谓吞吐量跟活跃性:就是单位时间内,系统处理信息的数量,速度表现的结果。
所谓死锁:就是两个线程,我有你要的,你有我要的,互不相让,都等对方让出来,就死锁了。
分析情景
锁顺序死锁:所有的线程申请锁的顺序不一样,那么就存在一定概率发生死锁。
换言之,如果所有线程都以 相同的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题。
危害
死锁,小应用当然没事。如果你是商业应用的软件呢。比如淘宝,特么一个锁一天可能要上百亿次被执行。要命了这
看范例1
public class EasyDead {
private final Object mALock = new Object();
private final Ojbect mBLock = new Object();
public void wantB(){
synchronized(mALock) {
synchronized(mBLock) {
doSomethings();
}
}
}
public void wantA(){
synchronized(mBLock) {
synchronized(mALock) {
doSomethings();
}
}
}
}
在多线程情况下,同时有线程执行了wantA跟wangB,那么就会导致死锁。
看范例2---百度上有说的银行转账面试题
public void trans(Account fromAccount, Account toAccount, int money) {
synchronized(fromAccount) {
synchronized(toAccount) {
if (fromAccount.getMoney() < money) {
throws new Exception();
} else {
fromAccount.del(money);
toAccount.add(money);
}
}
}
}
这里如果有两个账户,同时进行存钱和取钱操作。。。那么不就卡住了。看起来不会卡。其实依旧会卡。这个环路可以弄的大一点。
这两个范例,一个是静态直观可以发现。一个是动态的。
皆因锁顺序引起。而这里的加锁顺序还是可以改变的。这并不影响
那么静态是可以弄成一样的。动态的话,就需要用一定的规则:比如说hashCode。利用System.identityHashCode来计算。按照计算后的值排序。虽然依旧有一定的概率会一样。但毕竟是小概率事件。锁顺序死锁+小概率事件===极小概率事件。大幅降低也不错。
修改修改,看范例3
private final Object mLock = new Object();
public void trans(Account fromAccount, Account toAccount, int money) {
int from = System.identityHashCode(fromAccount);
int to = System.identityHashCode(toAccount);
if(from > to) {
synchronized(fromAccount) {
synchronized(toAccount) {
if (fromAccount.getMoney() < money) {
throws new Exception();
} else {
fromAccount.del(money);
toAccount.add(money);
}
}
}
} else if(from < to) {
synchronized(toAccount) {
synchronized(fromAccount) {
if (fromAccount.getMoney() < money) {
throws new Exception();
} else {
fromAccount.del(money);
toAccount.add(money);
}
}
}
} else {
synchronized(mLock) {//#多加一个私有锁。里头的锁顺序就不要紧了
synchronized(toAccount) {
synchronized(fromAccount) {
if (fromAccount.getMoney() < money) {
throws new Exception();
} else {
fromAccount.del(money);
toAccount.add(money);
}
}
}
}
}
}
这里排个序,然后针对一样的情况,再额外加个锁,吞吐也不会有大的影响。
范例4:对象间悄悄藏起来的死锁。
class A {
private final B b;
public A (B b) {
this.b = b;
}
public synchronized void tellB_toDoSomething() {
this.b.doB();
}
public synchronized void doA() {
}
}
class B {
private A a;
public B(){
a = new A(this);
}
public synchronized void telA_toDoSomething() {
this.a.doA();
}
public synchronized void doB() {
}
}
在对象间的死锁,A让B做事,A先获取自己的锁,然后获取B的锁;此时B也让A做事,B先获取自己的锁,在获取A的锁。自然又是死锁了。
这种情况也是锁顺序导致的。就是藏的比较深。
资源死锁
之前说,如果Executors之间有线程依赖的。那么,我们会扩大线程池也需要慎重考虑超时策略。
这里就存在一个,线程饥饿等待的情况。就是资源死锁了。
另外,在线程池满的情况下,所有新任务进入等待状态,如果一个线程需要两个资源,另一个线程也需要这两个资源,也存在死锁。
解决策略
1.超时。设置锁超时,超时就释放
2.通过dump log分析,我们的程序应该都是有追踪的嘛。上传log到服务器。我们好分析
3.轮询策略。但是这很可能会损耗CPU资源。不断的访问。这终究是个下策。
4.设置线程优先级,Thread Priority还是很有意思的。有了优先级。会有改善。
5.轮询策略的失败会导致活锁
活锁,就是其实也是锁,就是不断的访问,不会完全锁死。但是这会消耗很大的资源。每次失败后都会重试。----------策略。每次失败后的重试时间随机化。
小结
死锁的存在不仅降低了稳定性。也影响的吞吐量活性。要谨慎。死锁一般是又加锁顺序不一致导致的。一旦不一致就会产生求之而不得,不得则等待的情况。也就死锁了。
不到一定有需要尽量不要使用锁。开放调用更易进行死锁分析。
同时,如果要使用锁,那么要尽量减少锁代码块。越少越好。动不动就保护一整块不合适。