java多线程(四)-juc包- 原子变量类atomic

原子变量类atomic简介

Atomic包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。

有4种类型的原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性,Atomic包里的类基本都是使用Unsafe实现的包装类.

原子更新基本类型类

使用原子的方式更新的基本类型,提供了以下3个类:

(1)AtomicBoolean:原子更新布尔类型

(2)AtomicInteger:原子更新整型

(3)AtomicLong:原子更新长整型

AtomicInteger

这里我们分析AtomicInteger的源码

public class AtomicInteger extends Number implements java.io.Serializable {
	
	//juc中大多使用unsafe中的包装进行操作
	 private static final Unsafe unsafe = Unsafe.getUnsafe();
	
	 private static final long valueOffset;
	
	//使用volatile保证可见性
	 private volatile int value;
	
	public final int get() {
        return value;
    }
    public final void set(int newValue) {
        value = newValue;
    }
	
	//使用CAS进行操作
	public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
	
	//原子性自增方法,在unsage底层中是采用cas进行尝试操作的。
	   public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
	
}

从AtomicInteger中我们发现 juc包不使用重量级锁synchonized,而是采用volatile加CAS来保证并发安全的。

示例

class Counter {
    private AtomicInteger value = new AtomicInteger(0);

    public int add(int m ) {
        return value.addAndGet(m);
    }

    public int dec(int m) {
        return value.addAndGet(-m);
    }

    public int get() {
        return value.get();
    }
}
public class AtomicIntegerTest {
    final static int LOOP = 10000;

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for(int i = 0 ; i< LOOP;i++) {
                    counter.add(1);
                }
            }
        };

        Thread t2 = new Thread(){
            @Override
            public void run() {
                for(int i = 0 ; i< LOOP;i++) {
                    counter.dec(1);
                }
            }
        };

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.get());

    }

}

原子更新数组

通过原子的方式更新数组里的某个元素,Atomic包提供了以下3类:

(1)AtomicIntegerArray:原子更新整型数组里的元素

(2)AtomicLongArray:原子更新长整型数组里的元素

(3)AtomicReferenceArray:原子更新引用类型数组里的元素

AtomicIntegerArray

参考自博客

在分析原子数组之前,我们先来了解Java对象的内存布局,Java对象由对象头和实例数据两部分组成。

下图中MarkWord包含对象的hashCode、锁信息、垃圾回收的分代信息等,占32/64位;Class Metadata Pointer表示一个此对象数据类型的Class对象(虚拟机中的Klass对象)的指针,占32/64位;ArrayLength是数组对象特有的内容,表示数组的长度,占32位。数组对象的实例数据是各个元素的值或引用,普通对象的实例数据是各实例字段的值或引用。另外为了快速内存分配、快速内存寻址、提高性能,Java语言规范要求Java对象要做内存对齐处理,每个对象占用的内存字节数必须是8的倍数,若不是则要填零补足对齐

在这里插入图片描述

从上图可以看出,字段与对象头之间的偏移量是固定的,只要知道字段的相对偏移量和对象起始地址,我们就能获取此字段的绝对内存地址(fieldAddress=objAddress+fieldOffset),根据此绝对内存地址,我们就能忽略访问修饰符的限制而可直接读取/修改此字段的值或引用。

数组对象的元素内存定址,相对对于普通对象的字段定址有些不一样,它要先计算出对象头的长度,作为基础偏移量;由于数组元素的数据类型是相同的,每个元素的值或引用所占内存空间是相同的,因此将元素值或引用或占内存作为每两相邻元素的相对偏移量。根据对象起始位置、基础偏移量、相邻元素相对偏移量及数组下标,就可以获取到某个元素值或引用的绝对内存地址(itemAddress=arrayAddress+baseOffset+index*indexOffset),进而通过绝对内存地址读取或修改此元素的值或引用。

源码分析

这里以JDK1.8的AtomicIntegerArray为例,对原子数组做出简单的实现原理分析。

(1) 主要字段
Unsafe是所有并发工具的基础工具类
private static final Unsafe unsafe = Unsafe.getUnsafe();
数组对象头到数组首元素间的地址偏移量
private static final int base = unsafe.arrayBaseOffset(int[].class);
//数组中相邻元素的地址偏移量的位移表示形式(若shift=x,那么相邻元素的偏移量就是"1<<shift")
private static final int shift;
//实际存放元素的数组
private final int[] array;

静态块主要是对静态常量shift初始化。

static {
    int scale = unsafe.arrayIndexScale(int[].class);//表示相邻元素的地址偏移量
    if ((scale & (scale - 1)) != 0)
        throw new Error("data type scale not a power of two");//非法的,scale一定是2的幂次方
    shift = 31 - Integer.numberOfLeadingZeros(scale);
}

scale表示相邻元素的地址偏移量,因为内存对齐的原因,scale一定是2n。而shift是相邻元素的地址偏移量的位移表示形式,Integer.numberOfLeadingZeros(scale)取scale进制前导零的个数,所以shift是scale二进制有效位数减1,即有效的0的个数。
如scale=16,即scale=0b00000000_00000000_00000000_00010000 , 那么shift=4。

(2) 方法

构造方法

AtomicIntegerArray(int)创建指定长度的原子数组,AtomicIntegerArray(int)利用克隆(这里是深度克隆,源数组和原子数组互不影响)将指定的普通数组包装成原子数组.

public AtomicIntegerArray(int length) {
    array = new int[length];
}
public AtomicIntegerArray(int[] array) {
    // Visibility guaranteed by final field guarantees
    this.array = array.clone();
}

获取指定下标元素get(int)

get()方法主要利用byteOffset根据公式offset=base+i*scale求出数组对象的起始位置下标元素的偏移量offset,然后利用unsafe.getIntVolatile根据公式indexAddr=arrayAddr+offset求出出指定偏移位置的元素值

  public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }
    private long checkedByteOffset(int i) {//下标检查,并计算数组对象起始位置至此下标元素的偏移量
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);
        return byteOffset(i);
    }
    private static long byteOffset(int i) {//计算偏移量
        return ((long) i << shift) + base; //i*2^shift +base 即,"i*元素间距+对象头长度"
    }

    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);//利用unsafe方法求出指定偏移位置的int值
    }

更新指定下标元素set(int,int)

set()方法与get方法类似,都要先调用checkedByteOffset计算偏移量offset,不过此set方法要调用putIntVolatile在指定内存位置修改值而已。

public final void set(int i, int newValue) {
    unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}

getAndSet(int,int)

先获取值再设置新值,同样先调用checkedByteOffset计算偏移量offset,然后getAndSetInt进行CAS自旋更新指定的元素。

public final int getAndSet(int i, int newValue) {
    return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}
public final int getAndSetInt(Object o, long offset, int newValue) {//unsafe的方法
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, newValue));
    return v;
}

compareAndSet()CAS更新期望的元素,先调用checkedByteOffset计算偏移量offset,然后调用compareAndSwapInt尝试CAS更新元素。

public final boolean compareAndSet(int i, int expect, int update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

private boolean compareAndSetRaw(long offset, int expect, int update) {
    return unsafe.compareAndSwapInt(array, offset, expect, update);
}

使用示例

public class AtomicIntegerArrayTest {
    public static void main(String[] args) {
        AtomicIntegerArray array = new AtomicIntegerArray(new int[]{45,23,13,47,12,42});
        for(int i= 0; i < args.length; i++) {
            final int j = i;
            new Thread(() -> {
                array.compareAndSet(j,13,31);
                array.getAndAdd(j,2);
                array.decrementAndGet(j);
                array.getAndAdd(j,array.get(j) -1);
            }).start();

        }
        System.out.println(array);
    }
}

原子更新引用类型

原子更新基本类型AtomicInteger,只能更新一个变量,如果要更新多个变量,就需要使用这个原子更新引用类型提供的类,Atomic包提供了以下3个类。

(1)AtomicReference:原子更新引用类型

(2)AtomicReferenceFileldUpdater:原子更新引用类型里的字段

(3)AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型 只需要将相应的对象设置进原子更新引用类型里面,然后调用compareAndSet方法进行原子更新操作,实现原理同AtomicInteger里的compareAndSet方法

原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新

(1)AtomicIntegerUpdater:原子更新整型的字段的更新器

(2)AtomicLongFieldUpdater:原子更新长整型字段的更新器

(3)AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能发现的ABA问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值