关于Java自增操作的原子性

上一篇 / 下一篇  2012-05-25 09:12:43 / 个人分类:Java

最近在工作中和一个同事因为自增是不是原子性操作争论的面红耳赤,那Java的自增操作到底是不是原子性操作呢,答案是否的,即Java的自增操作不是原子性操作。51Testing软件测试网)~&Y%i#s4J Rc%|:O5X

  1、首先我们先看看Bruce Eckel是怎么说的:

vv.a$U!T051Testing软件测试网7r_4r_!Ojs g

   In the JVM an increment is not atomic and involves both a read and a write. (via the latest Java Performance Tuning Newsletter)

MU K\!\,j:P*?~m0

9\@1BUJ#|Y?0  意思很简单,就是说在jvm中自增不是原子性操作,它包含一个读操作和一个写操作。

/yNZ o.fF6r051Testing软件测试网2i#xB!St.MKI"Q

  2、以上可能还不能让你信服,要想让人心服口服,就必须用代码说话。正如FaceBook的文化一样:代码赢得争论。那我们就看一段代码:51Testing软件测试网Nnm!@Ql"l

51Testing软件测试网B9hI f*]e

  以下的代码是用100个线程同时执行自增操作,每个线程自增100次,如果自增操作是原子性操作的话,那么执行完amount的值为10,000。运行代码之后,你会发现amount的值小于10,000,这就说明自增操作不是原子性的51Testing软件测试网2Y!j @Rh@ho| R

  1. /** 
  2.  *  
  3.  * @author renrun.wu 
  4.  */ 
  5. public class MultiThread implements Runnable {  
  6.     private int count;  
  7.     private int amount = 1;  
  8.       
  9.     public MultiThread() {  
  10.          count = 100;  
  11.     }  
  12.       
  13.     public MultiThread(int count) {  
  14.         this.count = count;  
  15.     }  
  16.       
  17.     @Override 
  18.     public void run() {  
  19.         for (int i = 0; i < count; i++) {  
  20.             amount++;  
  21.         }  
  22.     }  
  23.       
  24.     public static void main(String[] args) {  
  25.         ExecutorService executorService = Executors.newCachedThreadPool();  
  26.         MultiThread multiThread =new MultiThread();  
  27.         for (int i = 0; i < 100; i++) {  
  28.             executorService.execute(multiThread);  
  29.         }  
  30.         executorService.shutdown();  
  31.           
  32.         try {  
  33.             Thread.sleep(60000);  
  34.         } catch (InterruptedException e) {  
  35.             e.printStackTrace();  
  36.         }  
  37.         System.out.println(multiThread.amount);  
  38.     }  
  39. }
51Testing软件测试网 p)~S B`]c ? `

  3、如果以上还不能让你信服的话,也没关系。我们就把自增操作反编译出来,看看java字节码是怎么操作的

lww-s;u3g$v051Testing软件测试网?(p3nT%` bu9TX

  以下是一个简单的自增操作代码

)L&].l2iDS Z?2B0
  1. public class Increment {  
  2.     private int id = 0;  
  3. 51Testing软件测试网&t8s7DZ'N AW~6p\z
  4.     public void getNext(){  
  5.         id++;  
  6.     }  
  7. }
  8. 我们看看反编译之后的Java字节码,主要关注getNext()方法内部的Java字节码。

    bE N`P$z2[0

    2U@#@h'|0HJde0
    1. public class Increment extends java.lang.Object{  
    2.     public Increment();  
    3.       Code:  
    4. :   aload_0  
    5. :   invokespecial   #1//Method java/lang/Object."<init>":()V 
    6. :   aload_0  
    7. :   iconst_0  
    8. :   putfield        #2//Field id:I 
    9. :   return 
    10.  
    11.     public void getNext();  
    12.       Code:  
    13. :   aload_0   //加载局部变量表index为0的变量,在这里是this  
    14. :   dup                 //将当前栈顶的对象引用复制一份 
    15. :   getfield        #2//Field id:I,获取id的值,并将其值压入栈顶 
    16. :   iconst_1            //将int型的值1压入栈顶 
    17. :   iadd                //将栈顶两个int类型的元素相加,并将其值压入栈顶 
    18. :   putfield        #2//Field id:I,将栈顶的值赋值给id 
    19. :  return 
    20.  
    21.     }

    )J,}[aqpZd0  很明显,我们能够看到在getNext()方法内部,对于类变量id有一个先取值后加一再赋值的过程。因此,我们可以很肯定的说Java中的自增操作不是原子性的。51Testing软件测试网o$tE {7d[-J

    51Testing软件测试网;@4`[h6}+p

      4、也许你会问,那局部变量的自增操作是否是原子性的。好,我们在看看一下代码:

    3W!]#h;y Bq@l1M/\.n0

    (~ivD }ASA0

    lvn#p1X0
    1. public class Increment {  
    2.     public void getNext(){  
    3.     int id = 0;  
    4.         id++;  
    5.     }  
    6. }
    51Testing软件测试网K2L9R C;o v

      我们再看看反编译之后的Java字节码,主要还是关注getNext()方法内部的Java字节码。51Testing软件测试网-IS5| d}wa:f n A0d

    51Testing软件测试网-M1\/LPMX+TW j

    51Testing软件测试网 q7Cn i R

    1. public class Increment extends java.lang.Object{  
    2. public Increment();  
    3.   Code:  
    4. :   aload_0  
    5. :   invokespecial   #1//Method java/lang/Object."<init>":()V 
    6. :   return 
    7.  
    8. public void getNext();  
    9.   Code:  
    10. :   iconst_0  
    11. :   istore_1  
    12. :   iinc    11 
    13. :   return 
    14.  
    15. }
    51Testing软件测试网~y3H~,GE3k!D-Q*U

      与全局变量的自增操作相比,很明显局部变量的自增操作少了getfield与putfield操作。而且对于局部变量来说,它无论如何都不会涉及到多线程的操作,因此局部变量的自增操作是否是原子操作也就显得不那么重要了。

    riW5pHJ X0

TAG:

 

评分:0

我来说两句

Open Toolbar