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

同步数据结构之原子复合类

 
阅读更多

引言

前面介绍的原子更新器都只是针对单个对象或者字段元素进行原子更新操作,在序章中提到的第四类复合型的原子更新器可以将一个引用变量与其他变量绑定到一起,实现复合的原子更新操作,这类更新器有两个: AtomicMarkableReference、AtomicStampedReference。

 

AtomicStampedReference

AtomicStampedReference是为了解决CAS操作过程中的ABA问题,它将一个引用类似和一个int型的标识版本号的变量作为二元组合整体,所以AtomicStampedReference也被称为带版本戳的原子引用类型。每一次试图原子更新引用类型的时候都至少需要比较其携带的版本号,如果版本号不一致将会更新失败,当然更新的时候对应的版本号也会一起被更新。还是以源码开始进行分析吧。

public class AtomicStampedReference<V> {
    //静态内部类Pair将对应的引用类型和版本号stamp作为它的成员
    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
	
    //作为一个整体的pair变量被volatile修饰
    private volatile Pair<V> pair;

	//构造方法,参数分别是初始引用变量的值和初始版本号
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }
	
	....
	
	private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
    private static final long pairOffset =
        objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

    //获取pair成员的偏移地址
    static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
                                  String field, Class<?> klazz) {
        try {
            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        } catch (NoSuchFieldException e) {
            NoSuchFieldError error = new NoSuchFieldError(field);
            error.initCause(e);
            throw error;
        }
    }
}

由以上的源码可见, AtomicStampedReference内部有一个静态内部类Pair的实例变量pair来存放初始化AtomicStampedReference时传入的初始引用变量和初始版本号,注意它是被volatile修饰的,保证了其可见性;并且在每一次原子更新操作的时候都会生成新的pair实例替代旧的pair,即把真正的引用变量和版本号作为一个整体进行更新,所以在AtomicStampedReference内部还提前获取了该pair变量的偏移地址,为原子更新操作作准备。

 

接下来,看看它的方法。它的方法分为两类,一类是get方法,另一类就是原子更新方法:

//获取现在的引用类型变量的值
public V getReference() {
	return pair.reference;
}
//获取现在的版本号
public int getStamp() {
	return pair.stamp;
}
//同时获取版本号和引用变量,参数必须是一个长度的int数组,
//因为int型的版本号不能通过引用传递,所以通过参数指定的数组进行返回。
//而对于的引用变量则直接返回。
public V get(int[] stampHolder) {
	Pair<V> pair = this.pair;
	stampHolder[0] = pair.stamp;
	return pair.reference;
}

然后就是最重要的原子更新操作相关的方法:

//同时原子的更新对于的引用变量和版本号,当引用类型和版本号都和期望的值一致的时候。成功返回true
//根据原子类序章,该方法只保证对自己所操作的变量的原子性和可见性,没有其他的happen-before规则成立。
//这里的源码虽然看起来是直接调用了compareAndSet,但是不保证运行时不被JVM替换掉真正的实现方法。
public boolean weakCompareAndSet(V   expectedReference,
								 V   newReference,
								 int expectedStamp,
								 int newStamp) {
	return compareAndSet(expectedReference, newReference,
						 expectedStamp, newStamp);
}
//CAS原子更新操作,当引用类型和版本号都和预期的一致的时候进行原子更新,由于它是直接更新整个volatile修饰的pair变量,
所以满足可见性,有序性。成功返回true
public boolean compareAndSet(V   expectedReference,
							 V   newReference,
							 int expectedStamp,
							 int newStamp) {
	Pair<V> current = pair;
	return
		expectedReference == current.reference &&
		expectedStamp == current.stamp &&
		((newReference == current.reference &&
		  newStamp == current.stamp) ||
		 casPair(current, Pair.of(newReference, newStamp)));
}

//直接将原来的引用类型变量和版本号更改为新的值。
public void set(V newReference, int newStamp) {
	Pair<V> current = pair;
	if (newReference != current.reference || newStamp != current.stamp)
		this.pair = Pair.of(newReference, newStamp);
}
//当引用类型的值与期望的一致的时候,原子的更改版本号为新的值。该方法只修改版本号,不修改引用变量的值,成功返回true
public boolean attemptStamp(V expectedReference, int newStamp) {
	Pair<V> current = pair;
	return
		expectedReference == current.reference &&
		(newStamp == current.stamp ||
		 casPair(current, Pair.of(expectedReference, newStamp)));
}
//该方法是其他CAS方法的真正实现方法,其实就是通过unsafe的CAS方法将pair原子的更改为新的值。成功返回true
private boolean casPair(Pair<V> cmp, Pair<V> val) {
	return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

通过以上原子更新方法,可见 AtomicStampedReference就是利用了Unsafe的CAS方法+Volatile关键字对存储实际的引用变量和int的版本号的Pair实例进行更新。

 

AtomicMarkableReference

AtomicMarkableReference其实可以看作是 AtomicStampedReference的特例,因为AtomicMarkableReference是将一个引用变量和一个布尔变量作为一个二元组合整体,它的版本号相当于只有true和false,而不像AtomicStampedReference那样可以对每一次的更新都记录版本号,因为在有些场合我们只需要知道是否有人等我们关注的对象进行了更改,如果已经更改就不再作任何更改,例如对节点删除的标记,这是其实通过AtomicMarkableReference就能够达到我们的要求。

 

关于AtomicMarkableReference的源码,其实它和AtomicStampedReference的设计原理是一样的,只是将标识版本号的int型变量换成了布尔型的变量,还是以一个静态内部类Pair将这个引用变量和布尔变量组合在一起进行原子的更新。所以其源码就不再一一列举。

public class AtomicMarkableReferenceTest {

	public static void main(String[] args) {
		AtomicMarkableReference<String> amr = new AtomicMarkableReference<String>("ABC", false);
		
		boolean result = amr.attemptMark("ABC", true);//标记为true
		
		System.out.println(result);
		
		System.out.println(amr.isMarked());//返回是否被标记了,true
		
		AtomicStampedReference<String>  asr = new AtomicStampedReference<String>("123", 0);
		
		boolean seted = asr.compareAndSet("123", "456", 0, 1);//原子的更新原值和版本号
		System.out.println(seted);
		
		System.out.println(asr.getStamp());//获取新的版本号 1
	}

}

  

 

 

 

 

 

分享到:
评论

相关推荐

    数据结构同步练习册习题及答案

    ### 数据结构同步练习册习题及答案解析 #### 第1章 绪论 **1.1 学习指导** 在本章中,我们将探讨数据结构的基础知识,这为后续章节的学习奠定理论基础。 ##### 1.1.1 本章主要内容 1. **数据结构与抽象数据类型...

    现代电力通信系统的数字同步网建设

    同步网通常可以分为两大类:准同步方式和同步方式。准同步方式主要用于国际间的通信链路,每个节点都有独立设置的基准时钟(如铯原子钟),这些时钟的基准通常都能达到或超过G.811规定的基准钟标准,其频率准确度...

    计算机网络和数据库面试问题汇总

    6. SQL与NoSQL的区别:SQL数据库采用结构化查询语言,支持事务和关系型数据模型。NoSQL数据库不使用SQL语言,支持键值对、文档、列族或图形等数据模型,适用于大规模数据存储。 7. CAP理论:CAP理论指出,在一个...

    Java并发:同步容器&并发容器

    例如,尽管Vector的get()和remove()方法是同步的,但对容器的复合操作(如获取最后一个元素或删除最后一个元素)可能仍然需要额外的同步,因为这些操作不是原子性的。如果不加以注意,可能会导致数据不一致或运行时...

    Java并发编程实战

    第1章 简介 1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 ...第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注 参考文献

    Java 并发编程实战

    前 言 第1章 简介 1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 ...第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注

    MySQL 40题面试题及答案.docx

    索引是一种数据结构,用于加快查询性能。索引可以快速地定位指定的数据,减少查询时间。 * 索引的优点:加快查询速度、减少磁盘 I/O、降低 CPU 负载。 * 索引的缺点:占用存储空间、降低写入性能。 高并发高...

    Redis.docx

    - **Redis**:通过哈希等复合数据结构,能够在特定场景下提供更高的内存利用率。 ### 持久化机制 Redis提供了两种持久化方式: - **RDB(Redis Database Backup)**:定期保存数据库的快照,速度快但数据完整性较...

    Java高级之并发编程.md

    3. **不保证原子性**:虽然`volatile`提供了可见性和有序性保障,但并没有提供原子性,即复合操作仍然需要其他的同步手段来保证。 综上所述,Java 8 的新特性和并发编程中使用的原子类及`volatile`等概念都是现代...

    java并发个人心得

    但需要注意的是,`volatile`不能保证原子性操作,因此对于复合操作仍需使用`synchronized`或其他同步机制。 #### 7. Future与Callable 在Java中,`Future`接口表示异步计算的结果。通过`FutureTask`类或`...

    一文精通Java中的volatile关键字

    但若涉及到复杂的数据结构或需要多个操作的原子性,应考虑使用`synchronized`或`java.util.concurrent`包中的工具类。 5. 内存模型: Java内存模型(JMM)规定了线程如何访问和修改共享变量,`volatile`关键字正是...

    Java并发编程(5)volatile变量修饰符-意料之外

    在Java编程语言中,`volatile`关键字是一个非常重要的并发控制机制,它被用来修饰类的成员...在处理复杂的数据结构或涉及多步操作的场景时,需要考虑使用`synchronized`、`Atomic`类或其他并发控制手段来确保线程安全。

    Java八股文通常指的是在Java面试中经常会被问到的一些基础知识点和常见问题.pdf

    - **基本数据结构**:Java中有一些线程安全的数据结构,例如String、HashTable、ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、Vector和StringBuffer等。这些数据结构在设计时考虑到了多线程环境...

    软件体系结构课件-概述部分

    构件可以是原子的或复合的,它们通过接口(端口)与外部交互,而连接器(如管道、过程调用、事件广播等)负责构件间的通信。过程模型强调了系统构造的步骤和规则。 6. **"4+1" 视图模型**: Kruchten 提出的"4+1...

    数据库原理模拟试卷三.doc

    在该模型中,属性的复合类型除了基本类型外,还包括多集类型、数组类型、结构类型和集合类型,使得数据模型更加灵活,能够更好地表示复杂的数据结构。 【函数依赖】 函数依赖是一种描述属性间关系的规则,表示一个...

    postgresql

    1. **数据类型丰富**:PostgreSQL 支持多种数据类型,包括基本的数值、字符串、日期/时间类型,以及自定义复合类型、数组类型、XML、JSON等高级类型,这使得它在处理复杂的数据结构时非常灵活。 2. **ACID事务**:...

    120道Java面试题以及答案.doc

    解决这个问题需要仔细调整数据结构,以避免变量在内存中的紧密排列。 5. **忙等待(Busy Spin)** - 忙等待是一种在不释放CPU资源的情况下等待事件的技术,适用于需要低延迟且无其他工作可做的情况。它可以避免...

    24学时攻克c++

    - **标准模板库(STL)**:提供了一系列高效的数据结构和算法。 ### 四、高级主题 #### 1. 内存管理 - **动态内存分配**:new/delete操作符。 - **智能指针**:如std::unique_ptr、std::shared_ptr等。 - **RAII...

Global site tag (gtag.js) - Google Analytics