• 多线程系列二:原子操作


    什么是原子操作

    不可被中断的一个或者一系列操作

    count++并不是一个原子操作,存在从内存读count,然后把count+1,再把加1后的值刷回内存三个操作步骤

    实现原子操作的方式

    Java可以通过锁和循环CAS的方式实现原子操作

    循环CAS:在一个(死)循环【for(;;)】里不断进行CAS操作,直到成功为止(自旋操作即死循环)。

    CAS( Compare And Swap )  为什么要有CAS?

    Compare And Swap就是比较并且交换的一个原子操作,由Cpu在指令级别上进行保证。

    为什么要有CAS因为通过锁实现原子操作时,其他线程必须等待已经获得锁的线程运行完以后才能获得资源,这样就会占用系统的大量资源

    CAS包含哪些参数?

    CAS包含三个参数:

    1、变量所在内存地址V

    2、变量当前的值A

    3、我们将要修改的值B

    如果说V上的变量的值是A的话,就用B重新赋值,如果不是A,那就什么事也不做。

    CAS实现原子操作的三大问题

    1、 ABA问题:其他的线程把值改成了C,很快改成了A,此时当前线程去修改值时就会认为A的值没有改变而继续修改成B,这样会有问题。

      解决ABA问题:引入版本号:1A-2C-3A,每次修改都加个版本号 发现版本号变了 说明值被改了就继续CAS操作

    2、 循环时间很长的话,cpu的负荷比较大

    3、 对一个变量进行操作可以,同时操作多个共享变量有点麻烦,

           解决方法:java提供了一个类AtomicReference来把几个变量写到类里面作为成员变量进行操作

    CAS线程安全(面试点)

    通过硬件层面的阻塞实现原子操作的安全

    拿AtomicInteger的CAS来举例,调用的是C的底层方法,所以是通过硬件层面的阻塞实现原子操作的安全

     

    原子更新基本类型类

    AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference

    AtomicInteger的常用方法如下

    ·int addAndGet(int delta): 进行添加操作以后获取加操作之后的值

    ·boolean compareAndSet(int expect,int update): 比较并且切换如果当前值是expect就修改为update

    ·int getAndIncrement(): 原子递增,但是返回的是自增以前的值

    incrementAndGet:原子递增,但是返回的是自增以后的值

    ·int getAndSet(int newValue): 获取修改前的值并设置新值

    package com.lgs.atomicint;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    // 原子更新基本类型类
    public class AtomicIntTest {
    
        static AtomicInteger ai = new AtomicInteger(1);
        
        public static void main(String[] args) {
            System.out.println("获取当前值:" + ai.get());
            System.out.println("先获取当前值再进行加1操作:" + ai.getAndIncrement());
            System.out.println("先进行加一操作再获取加1后的值:" + ai.incrementAndGet());
            System.out.println("获取当前值:" + ai.get());
            System.out.println("先获取当前值再进行设置操作:" + ai.getAndSet(8));
            System.out.println("获取当前值:" + ai.get());
        }
    }

    输出:

    获取当前值:1

    先获取当前值再进行加1操作:1

    先进行加一操作再获取加1后的值:3

    获取当前值:3

    先获取当前值再进行设置操作:3

    获取当前值:8

    原子更新数组类

    AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

    AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,

    其常用方法如下。

    ·int addAndGet(int i,int delta): 

    ·boolean compareAndSet(int i,int expect,int update): 

    数组通过构造方法传入,类会将数组复制一份,原数组不会发生变化。

    package com.lgs.atomicarray;
    
    import java.util.concurrent.atomic.AtomicIntegerArray;
    
    // 原子更新数组类
    // 数组通过构造方法传入,类会将数组复制一份,原数组不会发生变化。
    public class AtomicArray {
    
        // 初始化一个数组
        static int[] array = new int[] {1,2};
        // 把初始化的数组传入原子整型数组里面
        static AtomicIntegerArray aia = new AtomicIntegerArray(array);
        
        public static void main(String[] args) {
            System.out.println("获取数组里第一个值并设置为3:" + aia.getAndSet(0, 3));
            System.out.println("获取数组里第一个值:" + aia.get(0));
            // 数组通过构造方法传入,类会将数组复制一份,原数组不会发生变化。
            System.out.println("获取原始数组里第一个值:" + array[0]);
        }
    }

    输出:

    获取数组里第一个值并设置为3:1

    获取数组里第一个值:3

    获取原始数组里第一个值:1

    原子更新引用类型提供的类。

    ·AtomicReference 可以解决更新多个变量的问题

    ·AtomicStampedReference解决ABA问题 使用数字作为版本 关心得是有几个人改过

    ·AtomicMarkableReference解决ABA问题 使用Boolean作为版本,关心的是有没有修改过

    package com.lgs.atomiref;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    // 原子更新引用类型提供的类
    public class AtomicRef {
    
        // 定义一个原子更新引用类型User
        static AtomicReference<User> userRef = new AtomicReference<>();
        
        public static void main(String[] args) {
            User user = new User("lgs", 18);
            userRef.set(user);
            System.out.println("更新前的名字:" + userRef.get().getName());
            System.out.println("更新前的年龄:" + userRef.get().getOld());
            User updateUser  = new User("ll", 19);
            // 比较并且切换, 如果当前值是user就更新为updateUser
            userRef.compareAndSet(user, updateUser);
            System.out.println("更新后的名字:" + userRef.get().getName());
            System.out.println("更新后的年龄:" + userRef.get().getOld());
        }
        
        // 先定义一个类型
        static class User{
            // 姓名
            private String name;
            
            // 年龄
            private int old;
            
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public int getOld() {
                return old;
            }
    
            public void setOld(int old) {
                this.old = old;
            }
        }
    }

    输出:

    更新前的名字:lgs
    更新前的年龄:18
    更新后的名字:ll
    更新后的年龄:19

    原子更新字段类

    Atomic包提供了以下3个类进行原子字段更新。

    ·AtomicReferenceFieldUpdater: 

    ·AtomicIntegerFieldUpdater: 

    ·AtomicLongFieldUpdater:

    违反了面向对象的原则,一般不使用

    getAndAdd(i, delta)

  • 相关阅读:
    为Android编译bash
    编译toybox
    RGB信仰灯
    如何用Fiddler抓BlueStacks的HTTPS包
    Adobe Acrobat快捷方式
    [MS-SHLLINK]: Shell Link (.LNK) Binary File Format
    BZOJ 3993 星际战争
    BZOJ 3996 线性代数
    BZOJ 1797 最小割
    BZOJ 2726 任务安排
  • 原文地址:https://www.cnblogs.com/leeSmall/p/8227669.html
Copyright © 2020-2023  润新知