天天看海

每天都想看海

一个能够证明i++在多线程下非原子化的例子及其改进

主程序运行1000个子线程(太少了结果不容易出来),每个子线程执行指向同一整数引用的i++,当1000个子线程运行完毕后,主线程继续运行,输出最终的i值。很显然,如果最终输出的结果i值不等于1000,那么中间i++操作一定被分解了。程序代码如下:

【版本一】

import cn.yuzhe.multi.util.AllFinished;

public class ThreadSafeClass {

public static void main(String[] args) {
         
//要创建的子线程数
             int threadNum=1000;                          
  
         
//每个子线程都要引用的对象
             A_Integer bb=new A_Integer();
  
        
//创建能判断所有子线程是否结束的对象,用于保证主线程能在多有子线程都结束后再执行余下的代码,
             
//防止主线程提前于子线程结束
             AllFinished af=new AllFinished(threadNum);  
  
         
//创建多个子线程对象
             NoAtomicIntegerThread[] at=new NoAtomicIntegerThread[threadNum];
  
             
for(int i=0;i<threadNum;i++){
                   at[i]
=new NoAtomicIntegerThread(bb,i,af);
             }

  
             
for(int i=0;i<threadNum;i++){
                   at[i].start();
             }
 
  
             
while(true){
                   
if( af.isAllFinished() ){
                         System.out.println(
"主线程运行结束,应该获得的最终值为"+threadNum
                                          
+";实际最终值为"+bb.int_I);
                         
break;
                   }

   
                  
int sleep=500;    //睡眠一段时间,释放时间片
                  try {
                        Thread.sleep(sleep);
                  }
 catch (InterruptedException e) {
                        e.printStackTrace();
                  }

             }
 
         }

}


class NoAtomicIntegerThread extends Thread{
          
public A_Integer jj;
          
private int id;
          
private AllFinished af;

public void run() {
             jj.int_I
++;
             af.setItemFinished(id);
//           System.out.println(jj.int_I);              //输出中间结果
}


public NoAtomicIntegerThread(A_Integer __j,int _id,AllFinished _af) {
             
super();
             
this.jj=__j;
             
this.id=_id;
             
this.af=_af;
}

}


class A_Integer
{
            
public int int_I=0;
}



这里要用到一个我自己定义的util类:

/**
@author 红楼无梦,JAVA多线程QQ群:34237757
* Date:2008-8-13 09:28
* Function: 主线程创建多个分线程,保证主线程在所有分线程都结束后才能运行余下的代码,直至结束。
*/

public class AllFinished {
            
private boolean[] isItemsFinished;
            
private int num;

            
public AllFinished(int itemNum) {
                  
super();
  
                  num
=itemNum;
                  isItemsFinished
=new boolean[num];
                  
for(int i=0;i<num;i++)
                        isItemsFinished[i]
=false;
            }


            
public synchronized boolean setItemFinished(int i)
            
{
                  
if(i>=num)
                        
return false;
                  
this.isItemsFinished[i]=true;
                  
return true;
            }


            
public synchronized boolean isAllFinished()
            
{
                  
for(int i=0;i<num;i++){
                        
if(!this.isItemsFinished[i])
                              
return false;
                  }

                  
return true;
            }

}


例子需要运行多次才能看到结果,当实际结果和理想结果不一样的时候,很显然,i++被分解了,结果的部分输入如下:
......
996
997
998
主线程运行结束,应该获得的最终值为1000;实际最终值为998。

=====================================================
【改进一】下面的代码用AtomicInteger进行了改进,使得i++操作被原子化,代码如下:

public class ThreadSafeClass {

          
public static void main(String[] args) {
      
//要创建的子线程数
          int threadNum=1000;                          
  
      
//每个子线程都要引用的对象
          AtomicInteger bb=new AtomicInteger(0);
  
      
//创建能判断所有子线程是否结束的对象,用于保证主线程能在多有子线程都结束后再执行余下的代码,
          
//防止主线程提前于子线程结束
          AllFinished af=new AllFinished(threadNum);  
  
      
//多个子线程对象
          AtomicIntegerThread[] at=new AtomicIntegerThread[threadNum];
  
          
for(int i=0;i<threadNum;i++){
               at[i]
=new AtomicIntegerThread(bb,i,af);
          }

  
          
for(int i=0;i<threadNum;i++){
               at[i].start();
          }
 
  
          
while(true){
               
if(af.isAllFinished()){
                    System.out.println(
"主线程运行结束,应该获得的最终值为"+threadNum
                                              
+";实际最终值为"+bb);
                
break;
               }

   
               
int sleep=(int)(Math.random()*500);
               
try {
                    Thread.sleep(sleep);
               }
 catch (InterruptedException e) {
                    e.printStackTrace();
               }

          }
 
}

}


class AtomicIntegerThread extends Thread{
        
private int id;
        
private AllFinished af;
        
public AtomicInteger j;

         
public AtomicIntegerThread(AtomicInteger __j,int _id,AllFinished _af) {
              
super();
              
this.j=__j;
              
this.id=_id;
              
this.af=_af;
         }



         
public void run() 
                j.getAndIncrement();
               af.setItemFinished(id);
               System.out.println(j); 
//这里输出没有同步,很可能会出现不一致,这里仅仅用来测试一下,正好能证明线程将操作分解^_^   
         }

}



运行了多次没有出现被分解的情况,最后我把线程数从一千改成一万,又增加到十万,并且运行多次,没有出现结果和预计结果不一致的情况。十万个线程啊!如果能分解的话早把程序分得七零八碎了,呵呵。某次结果如下:
......
99994
99993
99992
主线程运行结束,应该获得的最终值为100000;实际最终值为100000
=====================================================
【改进二】群友TITAN的方法更简单,在版本一的基础上加上一小段代码

class NoAtomicIntegerThread extends Thread{


      
public void run() {
          
synchronized(jj){
                    jj.int_I
++;
          }

            af.setItemFinished(id);
            System.out.println(jj.int_I);
}



}



我对比了一下改进一和改进二,在十万个线程并发情况下,二者的运行速度几乎没有差别。可见JAVA类库中的AtomicInteger还是很实用的。

大伙有什么改进的方法,欢迎大家提意见。yuzhe80@126.com

******************************************************************************
后话:这里为了保证主线程在子线程都执行完后再继续执行,特意定义了一个类AllFinished,记录每个子线程是否结束,其实大可不必,因为JAVA中对线程有join函数,在main函数中改成这样就可以了。

public class ThreadSafeClass {
       
public static void main(String[] args) {
      
//要创建的子线程数
       int threadNum=1000;                          
  
      
//每个子线程都要引用的对象
       AtomicInteger bb=new AtomicInteger(0);
  
      
//多个子线程对象
       AtomicIntegerThread[] at=new AtomicIntegerThread[threadNum];
  
       
for(int i=0;i<threadNum;i++){
            at[i]
=new AtomicIntegerThread(bb,i);
       }

  
       
long last=System.currentTimeMillis();
       
for(int i=0;i<threadNum;i++){
            at[i].start();
       }
 
  
       
for(int i=0;i<threadNum;i++){
            
try {
                 at[i].join();                                     
//保证主线程在所有子线程结束后再执行
            }
 catch (InterruptedException e) {
                 e.printStackTrace();
            }

       }
 
  
       System.out.println(
"主线程运行结束,应该获得的最终值为"+threadNum
       
+";实际最终值为"+bb+"程序运行时间"+(System.currentTimeMillis()-last)+"毫秒");

      }

}



而且这样修改,在性能上有很大提升,用我自定义的类AllFinished,运行1000个线程,平均时间为2100ms,而用join函数,平均时间为1600ms,性能提升了25%。以后还是推荐大伙使用join吧^_^

posted on 2007-08-13 16:56 天天看海 阅读(373) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

留言簿

文章档案

搜索

最新评论