大飞老师带你再看Java线程(九)

继上篇讲完多线程中的可见性,有序性之后,本篇我们来聊一下线程原子性。

原子性

原子性:线程在执行一个操作或者多个操作,一但开始执行,要么执行成功,要么执行失败。在执行的过程中不允许被其他线程打断(不会切换到其他线程)。看下面代码

int  a = 1;

原子操作(具有原子性),即使在并发环境下, 同一个时刻只有一个线程操作

count++;

非原子操作,这代码实际分3步操作:
1> 内存中获取count的值
2> 执行count+1操作
3> 加一结果赋值到count变量
不进行额外的设置,此时count++是非原子操作,并发环境下,同一时刻可以多个线程可以执行上面不同的步骤。

原子操作

实现原子操作方式最简单方式使用synchronized同步块或者lock机制

        synchronized (this){
            count++;
        }
        try {
            lock.tryLock();
            count++;
        }finally {
            lock.unlock();
        }

此处我们介绍另外一中原子操作:Java中的Atomic包提供的原子操作

原子操作类:Atomic*

Atomic包
JDK5之后,java提供了提供一套专用于原子操作的api,根据类用的用途大体分为4大类:原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。

基本类型:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
字段类型:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
引用类型:AtomicMarkableReference,AtomicStampedReference

此处我们仅仅讨论基本类型以及其操作原理

基本类型

先上代码:体验下如何使用
需求:1000个线程对共享变量自增操作, 每一个线程+1

public class Resource implements  Runnable {
    public int i= 0;  //普通成员变量自增
    public volatile int j = 0; //使用volatile修饰变量自增
    public AtomicInteger ai = new AtomicInteger(); //使用原子操作类自增
    public AutoCounter ct = new AutoCounter();  //自定义类使用synchronized限制自增

    //自增
    public void run() {
        try {
            Thread.sleep(10); //让效果更明显
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        i++;
        j++;
        ai.incrementAndGet();
        ct.count();
    }
}
public class AutoCounter {
    private int count = 0;
    //自增加一
    public synchronized  void count(){
        count++;
    }
    //获取count
    public synchronized int getCount(){
        return this.count;
    }
}
public class App {

    public static void main(String[] args) throws InterruptedException {
        Resource resource = new Resource();
        //启动1000线程自增1
        for (int i = 0; i < 1000; i++){
            new Thread(resource).start();
        }
        Thread.sleep(3000);
        System.out.println("普通成员变量i:" + resource.i);
        System.out.println("volatile变量j:" + resource.j);
        System.out.println("原子操作类变量:" + resource.ai.get());
        System.out.println("synchronized变量:" + resource.ct.getCount());

    }
}

结果:
普通成员变量i:989
volatile变量j:995
原子操作类变量:1000
synchronized变量:1000

普通成员变量i跟volatile修饰的变量都无法自增到1000, 说明2点:
1>成员变量线程不安全,
2>volatile操作做无法做到原子操作
而原子操作的类与synchronized修饰自定义类可以完成, 那么我们不经要想, 原子操作的类是怎么做到同步的呢?要解释这个,就得从CAS说起

CAS原理

CAS(compare and swap),用于解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存原值(V)、期望值(A)和新值(B)。如果V值等于A值,那么处理器会自动将V值更新为B值。否则,不做任何操作。

明白概念之后,我们看回AtomicInteger 类:
持有3个属性:
value:CAS中的内存原值(V), 在代码层面的显示值
Unsafe : unsafe类一个很特别的类,名字叫“不安全”,可以直接读写内存、获得地址偏移值、锁定或释放线程。
valueOffset:CAS中内存原值(V)的内存地址(偏移量), Unsafe类通过valueOffset值可以直接操作内存(硬件的寄存器内存)
静态代码块:
AtomicInteger 加载进内存之后,直接获取value值在内存的存储位置
自增方法:
incrementAndGet: 委托unsafe对象实现,返回安全的CAS期待值(A), 然后自加1并返回。

public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value; //cas中内存位置值-V值
    //比较特殊的一个类,直接操作内存
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset; //cas中V值的内存地址(偏移量)
    static {
        try {
            //获取对象某个属性的地址偏移值。
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //自增长并返回,由unsafe对象方法实现
    public final int incrementAndGet() {
        //获取到cas中的内存位置V值, 同时确保正确,然后加一返回
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

AtomicInteger 类的incrementAndGet方法委托Unsafe 进行CAS判断。
Unsafe 定义3个方法实现:
objectFieldOffset:本地方法,直接获取属性对象的内存地址
getAndAddInt:获取通过CAS判断的期望值
compareAndSwapInt:本地方法, 进行CAS判断

public final class Unsafe {
     //本地方法,可以认为直接操作内存地址,返回指定属性对象的值
    public native long objectFieldOffset(Field var1);
    //参数1:原子操作类对象(AtomicInteger )
    //参数2:CAS中的内存偏移地址(AtomicInteger  value属性内存地址)
    //参数3:自增步长
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5; //CAS中内存值(V)
        do {
            //通过原子操作类对象与value 属性内存地址获取cas中内存值(V)
            var5 = this.getIntVolatile(var1, var2);
            //循环操作,一直到CAS中的内存原值(V)与期望值一致,才进入下一步操作
            //这里可以理解为第一获取的内存中原值var5与下一次获取原值var5一致,
            //表示没有其他线程修改内存中的原值 
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
     //进行CAS判断
     //参数1:原子操作类对象(AtomicInteger )
     //参数2:CAS中的内存偏移地址(AtomicInteger  value属性内存地址)
     //参数3:CAS中的期望值(A)
     //参数4:CAS中的更新值(B)
     public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
}

参照上面的源码, 发现没有任何的同步限制,那又怎么就实现了同步操作呢?其实,在代码层面上虽然没做同步限制,但在底层操作中以及包含同步操作的意思在里面了:
底层不同的处理对CAS处理不一样,在X86平台,CPU给部分指令执行加锁操作,实现指令的原子操作,上文提到CAS就是利用CMPXCHG指令实现的,而CMPXCHG指令是原子操作指令,在同一时刻,仅让一个线程执行,那么CAS结果只有2种结果,要么成功,要么失败。如果对于CAS操作执行失败的线程,做循环执行CAS操作,那么一定能够成功。那么就有阻塞概念了。也可实现了同步机制。

到这,本篇就结束了, 有兴趣的朋友可以继续研究其他Atomic类。


猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/80907147