浅谈Java多线程编程中Boolean常量的同步问题
在JAVA中通过synchronized语句可以实现多线程并发。使用同步代码块,JVM保证同一时间只有一个线程可以拥有某一对象的锁。锁机制实现了多个线程安全地对临界资源进行访问。
同步代码写法如下:
代码1:
Objectobj=newObject();
...
synchronized(obj){
//TODO:访问临界资源
}
JAVA的多线程总是充满陷阱,如果我们用Boolean作为被同步的对象,可能会出现以下两种情况:
一.以为对一个对象加锁,实际同步的是不同对象。
代码2:
privatevolatileBooleanisTrue=false;
publichvoidaMethod(){
...
synchronized(isTrue){
isTrue=!isTrue;
//TODO:访问临界资源
isTrue=!isTrue;
}
...
}
咋一看上面的代码没有问题,由于使用了synchronized(isTrue)同一时间只能有一个线程访问临界资源,但事实并不是这样。因为false和true这两个常量对应着两个不同的对象。当isTrue产生变化时,很可能导致不同的线程同步了不同的对象。JAVA的自动装箱会将false变为Boolean.FALSE,将true变为Boolean.TRUE(同时这也说明了此处若将false改为Boolean.FALSE其结果也是一样的)。写一个以上情况的测试代码如下:
代码3:
publicclassBooleanTest{
privatevolatileBooleanisTrue=Boolean.FALSE;//此处用false也一样
publicvoidaMethod(){
for(inti=0;i<10;i++){
Threadt=newThread(){
publicvoidrun(){
synchronized(isTrue){
isTrue=!isTrue;
System.out.println(Thread.currentThread().getName()+"-isTrue="+isTrue);
try{
Doubleran=1000*Math.random();
Thread.sleep(ran.intValue());
}catch(InterruptedExceptione){}
if(!isTrue)System.out.println(Thread.currentThread().getName()+"-Oh,No!");
isTrue=!isTrue;
}
}
};
t.start();
}
}
publicstaticvoidmain(String...args){
BooleanTestbt=newBooleanTest();
bt.aMethod();
}
}
运行以上代码,不时的会看到"-Oh,No!",表示不同的线程同时进入到synchronized代码块中。
二.以为同步的是不同对象,实际是一个对象。
有时候我们可能希望在多个对象上进行同步,如果使用了Boolean作为被同步对象,很可能会导致本来应该没有关系的两个同步块使用了相同对象的锁。示例如下:
代码4:
privatevolatileBooleanaBoolean=Boolean.FALSE;
privatevolatileBooleananotherBoolean=false;
publicvoidaMethod(){
...
synchronized(aBoolean){
//TODO:访问临界资源1
}
...
}
publicvoidanotherMethod(){
...
synchronized(anotherBoolean){
//TODO:访问临界资源2
}
...
}
假设原本aMethod和anotherMethod分别会被两组没有关系的线程调用。但是由于Boolean.FALSE和false指向的是同一个对象,可能导致对临界资源2的访问被临界资源1阻塞了(反之亦然)。
以上两种情况说明,在使用同步块时,尽量不用使用Boolean对象作为被同步对象,不然可能会出现意想不到的问题,或者对以后的代码修改造成陷阱。
从此也可以看出,任何对常量的同步都是有风险的。如果一定要对Boolean进行同步,一定要用new操作符来创建Boolean对象。