前2天项目中的一个web应用,在做了几个操作后就死机,白板了,只能重启,找了半天没有错误日志,跟踪了一下业务操作,发现是前台页面的提交与后台的其他应用并发引起死锁操作,几人把该同志的代码评审了一下,讨论了一下同步的写法,发现其锁的实现是有漏洞的。在此也总结一下,可能对其他人有所帮助,当然,感觉这种实现应该是很早就该如此的。
业务场景是这样的:前台应用A,后台应用B,都需要同时对资源类C进行get和set操作,但前提是每个应用在get资源后一直到其set或不set退出之前,保持C资源为其应用独占,因为C资源的操作必须是看到什么才能做什么。
例:C资源数必须>=0,A 先get C 得到100的量,经过一些判断处理决定允许将C进行减50的操作,同时B也想对C进行减80的操作,这样一来,执行的正确顺序是
1)A get c=100
2)A 判断 是否允许-50
3)A set c=100-50
4)B get c=50
5)B 判断 是否允许-80
6)B 条件不允许 退出
测试的类代码如下:
public class ClassA extends Thread {
private String threadName = "";//线程名称
private int sleepTime = 10000 ;//线程sleep时间
private Object lock = new Object(); //锁
private int subValue = 0 ; //被减数
public ClassA(String name,int sleep,int subValue){
this.threadName = name ;
this.sleepTime = sleep ;
this.subValue = subValue ;
}
public void run(){
System.out.println(threadName + " is running ");
synchronized (lock){
System.out.println(threadName + " get self lock ");
synchronized (ClassC.getLock()){
int value = ClassC.getValue();
System.out.println(threadName + " get C.value = "+value );
try{
//线程sleep 模拟应用的其他处理
System.out.println(threadName + " now is sleeping ");
Thread.sleep(sleepTime);
}catch(InterruptedException e){
e.printStackTrace();
}
if(value >= this.subValue){
int newValue = value - this.subValue ;
ClassC.setValue(newValue);
System.out.println(threadName + " set new value = " + newValue);
}else{
System.out.println(threadName + " can't set new value,now is = "+ value) ;
}
}
}
}
public static void main(String[] args) throws Exception {
ClassA a = new ClassA("appA",6000,50);
a.start();
ClassA b = new ClassA("appB",2000,80);
b.start();
}
}
class ClassC {
private static int value = 100 ;
private static Object lock = new Object();
public static Object getLock(){
return lock ;
}
public static int getValue(){
return value ;
}
public static void setValue(int newValue){
value = newValue ;
}
}
当前代码执行后结果如下:
appA is running
appB is running
appB get self lock
appA get self lock
appA get C.value = 100
appA now is sleeping
appA set new value = 50
appB get C.value = 50
appB now is sleeping
appB can't set new value,now is = 50
我们发现的错误是在ClassA的lock使用上,当时的开发人员写的代码是下面这行:
private String lock = ""; //锁
ClassA的锁和ClassC的锁用的都是字串,而不是Object,执行的结果就变成了:
appA is running
appB is running
appB get self lock
appB get C.value = 100
appB now is sleeping
appB set new value = 20
appA get self lock
appA get C.value = 20
appA now is sleeping
appA can't set new value,now is = 20
结果appB先把100减去80变成20,appA则不能用了
在试验时,大家对这一结果颇感不解,后经一人提醒,相同值的String 对象jvm会进行优化,使用相同的内存地址,以下这句话可以打印true
String a="123";
String b="123";
System.out.println( a == b);
所以,自我总结,应该在java中使用同步操作时,选取的对象最好是一个唯一的对象,以防止大家使用相同的对象,而使并发锁在同一个对象上,造成死锁。