zoukankan      html  css  js  c++  java
  • 原子性

        我们先聊聊一个经典面试题:i=i++; 我们知道在虚拟机底层它实际上做了三步:

        int temp =i; i = i + 1; i = temp;

        i++实际上的操作分为三个部分:读、改、写

        我们看看下面的例子:

    1. public class TestAtomicDemo {  
    2.     public static void main(String[] args) {  
    3.         AtomicDemo ad = new AtomicDemo();  
    4.             
    5.         for(int i = 0;i<10;i++) {  
    6.             new Thread(ad).start();  
    7.         }  
    8.     }  
    9. }  
    10.     
    11. class AtomicDemo implements Runnable{  
    12.     private int serialNumber = 0;  
    13.         
    14.     @Override  
    15.     public void run() {  
    16.         // TODO Auto-generated method stub  
    17.         try {  
    18.             Thread.sleep(200);  
    19.         }catch(InterruptedException e) {  
    20.                 
    21.         }  
    22.             
    23.         System.out.println(Thread.currentThread().getName()+":"+getSerialNumber() );  
    24.     }  
    25.     
    26.     public int getSerialNumber() {  
    27.         return serialNumber++;  
    28.     }  
    29.     
    30.     public void setSerialNumber(int serialNumber) {  
    31.         this.serialNumber = serialNumber;  
    32.     }  
    33.         
    34.         
    35. }  

        这里很明显,两个数据相同了,当然这不是每次都会发生的。为什么会发生这种情况?

        为了方便我们将变量名写成Num,就只画四个线程,根据我们之前说的,num先被线程1读取,然后执行改、写操作,在线程1还没来得及将值写回主存时,线程2读取了主存中的num,此时num依然等于0,这就发生了上面很尴尬的场景了。

        我们可以用上一讲谈到的volatile解决吗?当然不行,volatile解决的是可见性,然而我们这里有两步操作,即使是直接在主存操作,依旧可能出现上述情况,即线程1刚刚把num加完正打算写之前,线程2读取了当前num的值,最后依然出现两个1,因为i++存在读改写三步操作,本来这些操作不可分割,但是现在分割执行,破坏了原子性,我们现在需要用到原子变量。

        JDK1.5之后,java.util.concurrent.atomic为我们提供了大量的原子变量,这些变量有以下几个特点:

    1. 含有volatile的特性,原子变量封装的值都是volatile,保证内存的可见性,随便开个源码都可看到:

    2. CAS(Compare-And-Swap)算法保证数据原子可见性

      CAS算法是硬件对并发操作共享数据的支持

      CAS包含了三个操作数:

      内存值V 预估值A 更新值B

      当且仅当V==A时,V=B,否则将不会做任何操作

        

        那么CAS如何解决原子性问题呢?

        当线程1改完值,将主存中的num值修改为1,此时线程2需要进行判断,发现原值与现在值不同,所以什么都不做。这样只会使一个线程修改num,更重要的是CAS算法比普通的同步锁效率高很多,当CAS判定失败,它会放弃对CPU的占用,然后再进行判断。缺点就是我们要手工写一些代码,比如重新判断。

        扯了这么多,我们回到原先的题目,使用AtomicInteger来处理原问题的原子性,

    1. import java.util.concurrent.atomic.AtomicInteger;  
    2.     
    3. public class TestAtomicDemo {  
    4.     public static void main(String[] args) {  
    5.         AtomicDemo ad = new AtomicDemo();  
    6.             
    7.         for(int i = 0;i<10;i++) {  
    8.             new Thread(ad).start();  
    9.         }  
    10.     }  
    11. }  
    12.     
    13. class AtomicDemo implements Runnable{  
    14.     private AtomicInteger serialNumber = new AtomicInteger(0);  
    15.         
    16.     @Override  
    17.     public void run() {  
    18.         // TODO Auto-generated method stub  
    19.         try {  
    20.             Thread.sleep(200);  
    21.         }catch(InterruptedException e) {  
    22.                 
    23.         }  
    24.             
    25.         System.out.println(Thread.currentThread().getName()+":"+getSerialNumber() );  
    26.     }  
    27.     
    28.     public int getSerialNumber() {  
    29.         return serialNumber.getAndIncrement();  
    30.     }     
    31. }  
  • 相关阅读:
    消息中间件(一)MQ详解及四大MQ比较
    SIP协议
    PAT (Basic Level) Practice 1008 数组元素循环右移问题
    LeetCode-Algorithms 1. 两数之和
    PAT (Basic Level) Practice 1040 有几个PAT
    PAT (Basic Level) Practice 1023 组个最小数
    PAT (Basic Level) Practice 1021 个位数统计
    PAT (Basic Level) Practice 1007 素数对猜想
    PAT (Basic Level) Practice 1006 换个格式输出整数
    PAT (Basic Level) Practice 1004 成绩排名
  • 原文地址:https://www.cnblogs.com/figsprite/p/10780233.html
Copyright © 2011-2022 走看看