`
春花秋月何时了
  • 浏览: 41772 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

同步数据结构之原子数组类

 
阅读更多

引言

通过原子类序章我们知道Java并发包提供的原子类共分5类,这里开始介绍第二类数组类,其实也就是通过数组下标原子更新基本类型和引用类型的数组中的元素,它们是:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray。由于AtomicLongArray与AtomicIntegerArray非常类似,所以我还是重点对AtomicIntegerArray和AtomicReferenceArray进行分析。

 

AtomicIntegerArray

在分析其具体原子更新方法之前,我们有必要先了解一下AtomicIntegerArray的构造方法和一些确定数组下标的基础方法,这将有利于我们对后面其它所有方法的理解。

 

首先构造方法

private final int[] array;  //内部维护了一个final修饰的整形数组

public AtomicIntegerArray(int length) {
	array = new int[length]; //构造一个指定长度的所有元素都是0的整形原子数组
}

public AtomicIntegerArray(int[] array) {
	// Visibility guaranteed by final field guarantees
	this.array = array.clone();//构造一个和指定数组长度、内容一样的数组
}

它有两个构造方法, 注意第二个有实际内容的构造方法这里并没有使用volatile写来保证可见性,但是由于成员属性array是final关键字修饰的,根据Java内存模型JMM之五final内存语义对final域的JMM规则,只要没有在构造方法中逸出this指针,对final域的写入也能够在构造完成将this引用赋值给其它变量时变得立即可见。

 

基础方法

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);//第一个元素的偏移地址
private static final int shift;

static {
	int scale = unsafe.arrayIndexScale(int[].class);//数组中单个元素占用的内存空间
	if ((scale & (scale - 1)) != 0) //确保是2的幂,即2的多少次方
		throw new Error("data type scale not a power of two");
	shift = 31 - Integer.numberOfLeadingZeros(scale);
}

//更加下标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;//通过移位操作计算指定下标的数组元素偏移量
}
//通过下标获取数组元素,最终是将下标计算出偏移量
public final int get(int i) {
	return getRaw(checkedByteOffset(i));
}
//通过偏移量获取数组元素,使用了有volatile特性的get方法,可以立即获取最新的值
private int getRaw(long offset) {
	return unsafe.getIntVolatile(array, offset);
}
以上的基础方法分两部分,第一部分准备数组元素的基础偏移量和间隔偏移,按理说通过unsafe的arrayBaseOffset和arrayIndexScale两个方法就能确定需要的数据,但是事实上它的逻辑却更复杂,首先它要求scale必须是2的多少次方,对于int类型的数组其值为4,long类型的数组其值为8,然后它又通过 31 - Integer.numberOfLeadingZeros(scale) 方法计算出了一个新用于确定数组元素偏移地址的常量shift,关于numberOfLeadingZeros方法,可以很容易了解到它是利用二分查找方返回指定int值的二进制补码表示形式中在最高位的1左边的0的位数,比如:4的二进制补码为100,那么它的最高位1的左边的0的位数就是29;又如8的二进制补码为1000,那么其值就是28,然后再用31减该值,其实得到的结果就是二进制补码形式中最高位1右边的所有位数,为4的时候,shift就是2,为8的时候,shift就是4。

 

第二部分就是利用第一部分得到的常量计算出指定数组下标的元素的偏移量,它最终是通过位移操作完成的,例如,下标0的偏移就是base,int类型时,下标1的偏移1<<2 +4 等于8,下标2的偏移是2<<2 +4=12; long类型时,下标1的偏移1<<3 +8=16,下标2的偏移2<<3 +8=24,所以实际上和直接使用公式scale*i + base计算的结果是一样的,只是这里AtomicIntegerArray使用移位操作来计算结果罢了。

 

接下来,就是AtomicIntegerArray原子更新操作相关的方法了,其实它也主要由AtomicInteger所划分的那四类方法构成,不同的是AtomicIntegerArray更新的是数组中的特定元素而已。

 

1. 简单自更新 

//简单自更新方法1
public final int getAndIncrement(int i) {
	return getAndAdd(i, 1);
}
//简单自更新方法2
public final int getAndDecrement(int i) {
	return getAndAdd(i, -1);
}
//简单自更新方法3
public final int incrementAndGet(int i) {
	return getAndAdd(i, 1) + 1;
}
//简单自更新方法4
public final int decrementAndGet(int i) {
	return getAndAdd(i, -1) - 1;
}
//最终实现方法,都是利用了unsafe
public final int getAndAdd(int i, int delta) {
	return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
//unsafe的getAndAddInt实现
public final int getAndAddInt(Object o, long offset, int delta) {
	int v;
	do {
		v = getIntVolatile(o, offset);
	} while (!compareAndSwapInt(o, offset, v, v + delta));
	return v;
}
和 AtomicInteger的简单自更新几乎一样,不同的时此处需要传入更新的下标,然后对指定下标的数组元素进行加减1,它们的实现都是利用了unsafe的getAndAddInt方法:首先使用getIntVolatile获取最新的值,然后通过CAS原子地更新。

 

2. 简单外更新

//原子的将指定下标的元素更新为新的值
public final void set(int i, int newValue) {
	unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
//延迟可见设置指定下标的元素为新的值
public final void lazySet(int i, int newValue) {
	unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
}
//原子的设置指定下标的元素为新值,返回旧值
public final int getAndSet(int i, int newValue) {
	return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}
//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 final boolean weakCompareAndSet(int i, int expect, int update) {
	return compareAndSet(i, expect, update);
}
//原子的将指定下标的数组元素增加delta,返回旧值。
public final int getAndAdd(int i, int delta) {
	return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
//原子的将指定下标的数组元素增加delta,返回新值。
public final int addAndGet(int i, int delta) {
	return getAndAdd(i, delta) + delta;
}
//Unsafe的getAndSetInt实现
public final int getAndSetInt(Object o, long offset, int newValue) {
	int v;
	do {
		v = getIntVolatile(o, offset);
	} while (!compareAndSwapInt(o, offset, v, newValue));
	return v;
}
 它们的实现同样也是利用了CAS+Volatile关键字,不在熬述。

 

3. 函数式自更新 

public final int getAndUpdate(int i, IntUnaryOperator updateFunction) {
	long offset = checkedByteOffset(i);
	int prev, next;
	do {
		prev = getRaw(offset);
		next = updateFunction.applyAsInt(prev);
	} while (!compareAndSetRaw(offset, prev, next));
	return prev;
}
//返回新值
public final int updateAndGet(int i, IntUnaryOperator updateFunction) {
	long offset = checkedByteOffset(i);
	int prev, next;
	do {
		prev = getRaw(offset);
		next = updateFunction.applyAsInt(prev);
	} while (!compareAndSetRaw(offset, prev, next));
	return next;
}

 同AtomicInteger一样,AtomicIntegerArray也提供了函数式更新指定下标元素的方法,使用的IntUnaryOperator参数类型也是一样的,就不再熬述。

4. 函数式外更新 

public final int getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction) {
	long offset = checkedByteOffset(i);
	int prev, next;
	do {
		prev = getRaw(offset);
		next = accumulatorFunction.applyAsInt(prev, x);
	} while (!compareAndSetRaw(offset, prev, next));
	return prev;
}
//返回新值
public final int accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction) {
	long offset = checkedByteOffset(i);
	int prev, next;
	do {
		prev = getRaw(offset);
		next = accumulatorFunction.applyAsInt(prev, x);
	} while (!compareAndSetRaw(offset, prev, next));
	return next;
}

  同AtomicInteger一样,AtomicIntegerArray也提供了函数式更新指定下标元素的方法,使用的IntBinaryOperator参数类型也是一样的,就不再熬述。

  

同 AtomicIntegerArray相比AtomicLongArray不同的是内部维护的是一个long型的数组变量,其他都是一样的方法。

 

AtomicReferenceArray

说起AtomicReferenceArray,它提供的方法其实和AtomicIntegerArray也几乎类似,不同的是它在内部维护的是一个Object类型的数组,构造方法和通过下标定位数组元素的偏移地址的方式一摸一样。它也和AtomicReference一样分为那四类方法,同样没有AtomicIntegerArray和AtomicLongArray那样可以进行加减的操作方法。

 

另外,AtomicReferenceArray还有一个arrayFieldOffset变量:

 

private static final long arrayFieldOffset;
private final Object[] array;
static {
	try {
		unsafe = Unsafe.getUnsafe();
		arrayFieldOffset = unsafe.objectFieldOffset
			(AtomicReferenceArray.class.getDeclaredField("array"));
	} catch (Exception e) {
		throw new Error(e);
	}
}
 它表示内置对象数组的其实偏移地址,它只有一个用处那就是反序列化,就是如下这个方法:

 

/**
 * Reconstitutes the instance from a stream (that is, deserializes it).
 */
private void readObject(java.io.ObjectInputStream s)
	throws java.io.IOException, ClassNotFoundException,
	java.io.InvalidObjectException {
	// Note: This must be changed if any additional fields are defined
	Object a = s.readFields().get("array", null);
	if (a == null || !a.getClass().isArray())
		throw new java.io.InvalidObjectException("Not array type");
	if (a.getClass() != Object[].class)
		a = Arrays.copyOf((Object[])a, Array.getLength(a), Object[].class);
	unsafe.putObjectVolatile(this, arrayFieldOffset, a);
}

 

 

 
分享到:
评论

相关推荐

    学生基本信息管理 数据结构

    在IT行业中,数据结构是计算机科学的基础之一,它关乎如何高效地存储和处理数据。在这个“学生基本信息管理 数据结构”项目中,我们将会探讨如何利用数据结构和C++编程语言来设计一个系统,用于管理学生的基本信息。...

    多线程使用同一数组测试

    7. **并发模式(Concurrent Patterns)**:使用生产者-消费者模型,通过队列等数据结构来避免直接操作数组,从而实现线程间的协作。 8. **异步编程(Asynchronous Programming)**:利用VB.NET的异步等待(`Async`/...

    原子类测试

    - `AtomicReference`:存储对象引用的原子操作,可以实现无锁的数据结构。 2. 数组版本的原子类: - `AtomicIntegerArray`、`AtomicLongArray`和`AtomicReferenceArray`:分别对应整型、长整型和引用类型的数组,...

    一种采用Lock-Free同步机制的数据结构的研究.pdf

    在实际应用中,除了跳表以外,还有循环数组、堆、队列或循环链表等多种数据结构可以用于实现共享优先级队列。它们的同步实现通常依赖于锁机制,无论是粗粒度锁还是细粒度锁。粗粒度锁操作简单但并发效率低,而细粒度...

    Java concurrency之AtomicLongArray原子类_动力节点Java学院整理

    Java并发编程中的`AtomicLongArray`是`java.util.concurrent.atomic`包中的一种数据结构,它提供了一种原子性地操作长整型数组的方式。这个类主要用于在多线程环境中实现高效且线程安全的数组元素更新。下面我们将...

    并发编程——原子操作CAS.pdf

    通过例子代码,文档演示了如何利用这些类实现复杂数据结构中的线程安全操作。 在结束部分,文档提出了CAS操作的一些问题。例如,CAS操作虽然能保证操作的原子性,但当多个线程同时修改同一变量时,可能会发生ABA...

    JAVA数据结构

    通过对上述知识点的学习,我们不难发现数据结构作为计算机科学领域的基石之一,在实际项目开发过程中扮演着极其重要的角色。无论是基础知识还是高级技巧,都需要不断积累和实践才能达到熟练运用的水平。希望每位JAVA...

    基于Java的环形缓冲数组实现.zip

    这种数据结构利用数组的特性,提供了一种在固定大小内存空间内循环读写的数据容器。 环形缓冲区的核心思想是将一个固定大小的数组视为一个环,当数据写入到缓冲区尾部时,不再向后扩展而是从数组的开头继续写入,...

    算法、常用数据结构、文件资料、多线程和XML解析 资料汇总

    - **同步机制**:Java 提供了多种同步机制,如 `synchronized` 关键字、显式锁(`Lock` 接口)、原子变量类 (`AtomicInteger`, `AtomicLong`) 等。 ### 知识点五:XML 解析 - **XML 概念**:XML(Extensible ...

    java面试题及答案-非常全面(包括基础、网络、数据结构、算法及IT大厂面经)

    - **解题思路**:理解题目需求,选择合适的数据结构和算法。 ### C++ #### new/delete与malloc/free的区别 - **`new`/`delete`**:不仅分配/释放内存,还会调用构造/析构函数。 - **`malloc`/`free`**:仅分配/...

    常见的笔试和面试题目,涵盖了数据结构、算法、操作系统、网络、数据库.pdf

    #### 数据结构与算法 1. **数组与字符串** - **反转一个字符串**: - 字符串是不可变对象,在大多数编程语言中,通常需要借助额外的数据结构(如数组或列表)来实现字符串的反转。 - **找出数组中和为特定值的两...

    extended-list:无锁动态可调整大小的数组列表

    无锁数据结构,或称为锁-free数据结构,是通过原子操作(如CAS - Compare and Swap)来确保并发操作的正确性,而不是依赖于线程间的同步或锁定。这种设计模式使得多个线程可以同时进行读写操作,极大地提高了并发...

    Java多线程基础-03、数组定义方式二、元素默认值规则.rar

    例如,如果你想在多线程环境中安全地更新数组,可以使用`synchronized`关键字来同步对数组的访问,或者使用`AtomicIntegerArray`这样的并发类,它们提供了原子操作,从而避免了线程间的竞态条件。 总之,Java多线程...

    Java并发编程实践.pdf

    1. **无锁数据结构**:Amino实现了免锁(Lock-Free)的集合类,如队列,这使得在并发环境下数据结构的操作更加安全和高效。无锁数据结构通过避免锁的使用,减少了线程间的同步开销,从而提高了并发性能,降低了死锁...

    java performance

    4. **理解并发模型**:深入理解Java的并发模型(如volatile关键字的作用机制、原子操作的实现原理等),有助于更好地设计并行算法和数据结构。 ### 总结 通过对上述知识点的理解和实践,开发者可以在编写Java应用...

    JavaScript-Library:原子范围API的JS库

    这个JS库可能包含了对数组、对象或其他数据结构进行原子更新、读取和删除的方法,确保这些操作在并发环境下也能正确执行。 API的设计通常会包含以下几种原子操作: 1. **原子读取(Atomic Read)**:这类方法允许你...

    Java常见的线程安全的类.docx

    Java中的线程安全是多线程编程中一个重要的概念,主要涉及到并发访问数据结构时的正确性和一致性。线程安全的实现方式多种多样,包括使用synchronized关键字、原子类、阻塞队列以及特定的并发集合类等。下面将详细...

    ArrayList的底层原理Java系列2021.pdf

    这两个步骤并不是原子操作,在多线程环境下,如果不进行适当的同步控制,可能会出现一个元素被多个线程重复添加的情况,导致数组大小和实际存储的元素数量不一致。 为了解决这一线程不安全的问题,在多线程环境下...

    北京工业大学893软件工程学科专业基础2021年初试大纲.pdf

    - 广义表是一种复杂的数据结构,可以嵌套其他广义表或原子元素,支持多种遍历算法。 5. **树** - **知识点解析**: - 树是一种非线性的数据结构,由节点组成,具有层次关系。 - 二叉树是一种特殊的树,每个节点...

    java多线程.pdf

    这些数据结构在设计上考虑了多线程环境下的数据安全和访问效率,例如,CopyOnWriteArrayList在写操作时会复制整个数组,确保读操作能够不受影响,从而减少锁的使用。 线程设计模式包括Master-Worker模式、Producer-...

Global site tag (gtag.js) - Google Analytics