转载和引用,请注明原文出处! Fork me on GitHub
结局很美妙的事,开头并非如此!

多线程系列二:原子操作

什么是原子操作

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

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)

posted @ 2018-01-07 11:50  小不点啊  阅读(837)  评论(0编辑  收藏  举报