代码改变世界

[hyddd的FindBugs分析记录][M M IS] Inconsistent synchronization追加说明

2009-04-01 20:44  hyddd  阅读(3718)  评论(3编辑  收藏  举报

  前面已经写了一篇文档说明Inconsistent synchronization,但最近做代码安全时候又发现了一些关于Inconsistent synchronization的新问题,所以追加说明一下。

  我们先看一段较长的代码:

public class Test {    
    
public static void main(String[] args) throws InterruptedException{        
        Test2 t2 
= new Test2();
        MyThread thread1 
= new MyThread(t2,1);
        t1.start();
        
for(int i=0;i!=1000;i++){
            
new MyThread(t2,2).start();
        }
Thread.sleep(
4000);
        Integer a 
= t2.getA();
        System.out.println(
"total:" + a);
    }
}
class MyThread extends Thread {
    
public Test2 t2 = null;
    
public int num =0;
    
public MyThread(Test2 t2 , int num){
        
this.t2 = t2;
        
this.num =num;
    }
    
public void run(){
        
if(num == 1){
            
try {
                Integer a 
= t2.getA();
                Thread.sleep(
50);
                t2.cleanA();                
                System.out.println(
"before clean:"+ a.toString());
            } 
catch (InterruptedException e) {e.printStackTrace();}
        }
        
else if(num == 2){
            
try{
                t2.selfPlusPlus();
                Thread.sleep(
1);
            }
            
catch (Exception e) { e.printStackTrace();    }
        }
        
else
        {
            System.out.println(
"not run!");
        }
    }
}
class Test2 extends Thread {
    
private int a =100;    
    
public synchronized void selfPlusPlus(){ a++; }
    
public synchronized void cleanA(){ a=0;}
    
public int getA() {    return a;    }
}
运行后我们得出一个结果:

before clean:100

total:835

Before clean =100,说明了我们的thread1是最先获得t2中的a的值!

我们的启了1000个线程去对t2做selfPlusPlus(a++)操作,正常是应该得到值是1000的,结果却是:835。

请看下面分析:

  1.没有对变量 a做同步加锁,而是对操作的它的函数做同步加锁(如:public synchronized void selfPlusPlus(),public synchronized void cleanA()),这样做只能对同类操作加锁,不同类的操作,如:selfPlusPlus()和cleanA()互相之间同时对a的操作不会影响,看下面代码:

Public synchronized int test1(){
  A 
= A+1;
  //tag
  A = A+1;
  Return A
}
Public 
synchronized void test2(){
  A
=A-1;
}
cpu完全有可能在test1的"//tag"处进行线程切换,如果thread1和thread2都调用test1(),从thread1切换到thread2时,thread2发现资源被锁,则继续等待,cpu继续切换,然后thread1继续运行,但如果thread1调用test1(),而thread2调用teset2()呢,当在"//tag"处发生线程切换时,由于thread2资源没有被锁,故thread2可以正常执行,那么整个执行流程变为:A=A+1,A=A-1,A=A+1,结果是thread1的test1()返回了一个莫名其妙的数。
  2.进行原子操时没有加锁,第一个例子中,其实,getA()和cleanA()两者应该是一个原子操作,但没对操作的对象a加锁,导致出现问题。
我建议把上述代码修改为:

public void run(){
        
if(num == 1){
            
try {
         Synchronized(t2.lock){
                    Integer a 
= t2.getA();
                    Thread.sleep(
50);
                t2.cleanA();                
                    System.out.println(
"before clean:"+ a.toString());
      }
   //
       else if(num == 2){
         
try{
                t2.selfPlusPlus();
                 Thread.sleep(
1);
             }
   //
class Test2 extends Thread {
  //
  Object lock = new Object();
  //
      public void selfPlusPlus(){
      Synchronized(lock){
      a
++
            }
  }
  //