• 并发之无锁技术归纳


    并发之AtomicBoolean/AtomicBooleanArray/AtomicBooleanUpdateFeild
    1 和前面的AtomicInteger很相似或者原理基本一致的;原理就是使用了CAS算法实行循环重试的方式来保证一组操作是原子性的操作;
    2 同样的也是一个无锁技术的应用;
    3 在源码内部,使用1表示true,使用0表示false;
    同样的boolean类型的变量在并发情况下也是不安全的,因此使用了AtomicBoolean来保障原子性的操作;
    AtomicReference案例:
    和AtomicInteger、AtomicLong原大同小异;只是AtomicLong而言,32位的数分为高16位和低16位分别并行计算;
    AtomicReference是一种模板类,可以用来封装任何类型的数据;
    public class AtomicReference<V> implements java.io.Serializable
     
    package automic;
    import java.util.concurrent.atomic.AtomicReference;
    public class AtomicRefrenceTest {
         public final static AtomicReference<String> atomicString = new AtomicReference<String>("gosaint");
         /**
          * 创建了10个线程,同时去修改atomicString的值,但是在并发状态下只有一个可以修改成功!
          * @param args
          */
         public static void main(String[] args) {
             // 开启10个线程
             for (int i = 0; i < 10; i++) {
                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                           try {
                                Thread.sleep(Math.abs((int) Math.random() * 100));
                           } catch (Exception e) {
                                e.printStackTrace();
                           }
                           if (atomicString.compareAndSet("gosaint", "mrc")) {
                                System.out.println(Thread.currentThread().getId() + "Change value");
                           } else {
                                System.out.println(Thread.currentThread().getId() + "Failed");
                           }
                      }
                  }).start();
             }
         }
    }
     
    看运行结果:只有一个线程对值进行了修改;
    10Change value
    9Failed
    11Failed
    13Failed
    14Failed
    15Failed
    12Failed
    16Failed
    18Failed
    17Failed
    CAS的缺点:
        其实这个之前说过,为了深刻理解,这里再说明一下;比如在内存的初始值是3;现在存在两个线程去修改这个值;根据JVM内存模型;每一个线程都存在这个变量的副本;假设线程1去修改这个值;发现内存中的这个值是3;想要修改为4;但是此时线程2也去修改了内存的值;并且鲜牛该为2,再修改为3,此时线程1发现还是内存还是3,进行了修改;我们看到,虽然对于线程1来说,修改完成了,但是内存中的3确是经过修改2修改过的,这个值得状态已经发生了变化;如果我们对于某个值的状态很关注并且这个状态很重要;那么这个漏洞必须要避免;这也就是传说中的“ABA”问题;在JAVA中,通常是对内存中值得每一次修改添加一个时间戳作为版本号,再比较值的时候同样的时间戳的版本号也要比较;
    在AtomicStampedReference类中,存在一个内部类Pair来封装值和时间戳;
    private static class Pair<T> {
            final T reference;
            final int stamp;
            private Pair(T reference, int stamp) {
                this.reference = reference;
                this.stamp = stamp;
            }
            static <T> Pair<T> of(T reference, int stamp) {
                return new Pair<T>(reference, stamp);
            }
        }
     
    public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }
     
    上面的源码在比较值得时候时间戳的信息同样的也进行比较;
    package automic;
    import java.util.concurrent.atomic.AtomicStampedReference;
    public class AtomicTest {
         /**
          * AtomicStampedReference(V initialRef, int initialStamp)
          *   创建具有给定初始值的新 AtomicStampedReference。
          * @param args
          */
         //创建AtomicStampedReference,用户的账户是19,版本号是0
         static AtomicStampedReference<Integer> asr=new AtomicStampedReference<Integer>(19,0);
         public static void main(String[] args) {
             //创建3个线程给用户充话费
             for(int i=0;i<3;i++){
                  final int expectedStamp = asr.getStamp();//获取时间戳
                  new Thread(new Runnable() {
                      
                      @Override
                      public void run() {
                           while(true){
                                while(true){
                                    //获取值
                                    Integer expectedReference = asr.getReference();
                                    /**
                                     * 小于20元话费,那么充值20
                                     */
                                     if(expectedReference<20){
                                         if(asr.compareAndSet(expectedReference, expectedReference+20, expectedStamp, expectedStamp+1)){
                                              System.out.println("充值成功,余额为:"+asr.getReference());
                                              break;
                                         }
                                    }else{
                                         break;
                                    }
                                }
                           }
                      }
                  }).start();
             }
             //启动100个线程消费
             new Thread(new Runnable() {
                  
                  @Override
                  public void run() {
                      for(int i=0;i<100;i++){
                           while(true){
                                int stamp = asr.getStamp();
                                Integer reference = asr.getReference();
                                if(reference>10){
                                     if(asr.compareAndSet(reference, reference-10, stamp, stamp+1)){
                                          System.out.println("消费10元,余额:"+ asr.getReference());
                                 break;
                                    }
                                }else{
                                    break;
                                }
                           }
                           try{
                                 Thread.sleep(100);
                           }catch(Exception e){
                                
                           }
                      }
                      
                  }
             }).start();
         }
    }
    解释下代码,有3个线程在给用户充值,当用户余额少于20时,就给用户充值20元。有100个线程在消费,每次消费10元。用户初始有9元,当使用AtomicStampedReference来实现时,只会给用户充值一次,因为每次操作使得时间戳+1。运行结果:
    充值成功,余额为:39
    消费10元,余额:29
    消费10元,余额:19
    消费10元,余额:9
    

      

    代码修改:修改为AtomicReference类
    package automic;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    public class AtomicTest2 {
         /**
          * AtomicStampedReference(V initialRef, int initialStamp)
          *   创建具有给定初始值的新 AtomicStampedReference。
          * @param args
          */
         //创建AtomicStampedReference,用户的账户是19,版本号是0
         static AtomicReference<Integer> asr=new AtomicReference<Integer>(19);
         public static void main(String[] args) {
             //创建3个线程给用户充话费
             for(int i=0;i<3;i++){
                  new Thread(new Runnable() {
                      
                      @Override
                      public void run() {
                           while(true){
                                while(true){
                                    //获取值
                                    Integer expectedReference = asr.get();
                                    /**
                                     * 小于20元话费,那么充值20
                                     */
                                     if(expectedReference<20){
                                         if(asr.compareAndSet(expectedReference, expectedReference+20)){
                                              System.out.println("充值成功,余额为:"+asr.get());
                                              break;
                                         }
                                    }else{
                                         break;
                                    }
                                }
                           }
                      }
                  }).start();
             }
             //启动100个线程消费
             new Thread(new Runnable() {
                  
                  @Override
                  public void run() {
                      for(int i=0;i<100;i++){
                           while(true){
                                Integer reference = asr.get();
                                if(reference>10){
                                     if(asr.compareAndSet(reference, reference-10)){
                                          System.out.println("消费10元,余额:"+ asr.get());
                                 break;
                                    }
                                }else{
                                    break;
                                }
                           }
                           try{
                                 Thread.sleep(100);
                           }catch(Exception e){
                                
                           }
                      }
                      
                  }
             }).start();
         }
    }
     
    运行结果:出现了多次充值的现象:
    充值成功,余额为:39
    消费10元,余额:29
    消费10元,余额:19
    充值成功,余额为:39
    消费10元,余额:29
    消费10元,余额:19
    充值成功,余额为:39
    消费10元,余额:29
    充值成功,余额为:39
    消费10元,余额:39
    消费10元,余额:29
    充值成功,余额为:39
    消费10元,余额:39
     
     
     
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    使用SQL语句创建SQL数据脚本(应对万网主机部分不支持导出备份数据)
    js和jquery页面初始化加载函数的方法及先后顺序
    熔断器原理
    List<T>线性查找和二分查找BinarySearch效率分析
    ASP.NET资源大全-知识分享 【转载】
    C#语法——委托,架构的血液
    SUPERSOCKET 客户端
    VS 中的几种注释方法
    计算机专业术语中英文对照
    2018服务端架构师技术图谱
  • 原文地址:https://www.cnblogs.com/gosaint/p/9068195.html
Copyright © 2020-2023  润新知