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

同步数据结构之原子标量类

 
阅读更多

引言

通过原子类序章我们知道Java并发包提供的原子类共分5类,这里开始介绍第一类标量类,其实也就是原子更新基本类型和引用类型,它们是:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference. 它们提供的方法基本相同,其中AtomicBoolean最简单,其它三个提供的方法复杂度相当,这里我先以最常使用的AtomicInteger为例进行分析。

 

AtomicInteger

除了在序章中提到的方法之外,AtomicInteger主要由以下四类方法(我按照自己的理解取名划分的)构成对原子变量的更新操作。

 

1. 简单自更新

就是指没有外部变量参与的进行简单自身加减1的操作,这类方法包括如下几个方法:

  • int getAndIncrement(),以原子的方式将当前值加1,返回自增前的值;
  • int getAndDecrement(),以原子的方式将当前值减1,返回自减前的值;
  • int incrementAndGet(),以原子的方式将当前值加1,返回自增后的值;
  • int decrementAndGet(),以原子的方式将当前值减1,返回自减后的值;
    以下面getAndIncrement的源码为例,可以看出这类操作主要是利用了CAS+Volatile关键字的方式实现。
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
}

//Unsafe:
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;
}
 
2. 简单外更新
就是指有外部简单变量参与的对自身进行更新的操作,这类方法包括如下几个,当然在序章中介绍的那几个基本方法set、lazySet、compareAndSet、weakCompareAndSet也属于此类方法。
  • int getAndSet(int newValue),以原子的方式设置成新值newValue,并返回旧值;
  • int getAndAdd(int delta),以原子的方式将实例中的值(AtomicInteger里的value)增加delta,但是返回增加前的旧值;
  • int addAndGet(int delta),以原子的方式将实例中的值(AtomicInteger里的value)增加delta,返回增加后的新值;
它们的实现同样也是利用了CAS+Volatile关键字,不在熬述。
 
3. 函数式自更新
从JDK8开始,Java引入了函数式编程的概念,所以在JDK8中的原子类AtomicInteger提供了更加灵活的实现更加复杂的原子操作,这里我说的函数式自更新就是其中一种,它在不借助外部变量的情况下对变量自身进行更加复杂的逻辑运算,而不是传统的简单的加减1运算,这类方法是int getAndUpdate(IntUnaryOperator updateFunction) 和 int updateAndGet(IntUnaryOperator updateFunction) 分别对应了获取旧值并进行复杂的函数式运算和先进行函数式运算更新实例的值然后返回新值这两种操作类型,下面是这两个方法的源码:
public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
}

public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return next;
}
    从表面上看,以上两个方法看不出函数式编程的影子,只是传入了一个IntUnaryOperator变量,执行了它的applyAsInt方法,我们接着看IntUnaryOperator的源码:
@FunctionalInterface
public interface IntUnaryOperator {

int applyAsInt(int operand);

default IntUnaryOperator compose(IntUnaryOperator before) {
    Objects.requireNonNull(before);
    return (int v) -> applyAsInt(before.applyAsInt(v));
}

default IntUnaryOperator andThen(IntUnaryOperator after) {
    Objects.requireNonNull(after);
    return (int t) -> after.applyAsInt(applyAsInt(t));
}

static IntUnaryOperator identity() {
    return t -> t;
}
   IntUnaryOperator是一个接口,需要我们实现的方法正是applyAsInt方法,而且它的另外两个方法compose、andThen也直接调用该接口方法,这两个方法分别实现了前置和后置处理,详细的说就是,compose方法会以传入的IntUnaryOperator实例参数的applyAsInt执行结果为输入去执行自身的applyAsInt方法,也就是先执行参数的IntUnaryOperator实现,然后才执行自己的实现;而andThen先执行自己的实现,最后以其返回值作为输入执行参数的IntUnaryOperator实现,这两个方法不但实现了流式的函数式编程效果,而且还直接使用了Java8引入的Lambda表达式(->),利用IntUnaryOperator接口实现我们就可以实现复杂的自更新逻辑。
   下面以一个例子来说明IntUnaryOperator的简单使用方式:
public static void main(String[] args) {
	IntUnaryOperator add = new IntUnaryOperator(){

		@Override
		public int applyAsInt(int operand) {
			return operand + operand;
		}
	};
	
	IntUnaryOperator mul = new IntUnaryOperator(){

		@Override
		public int applyAsInt(int operand) {
			return operand * operand;
		}
	};
	
	int i = new AtomicInteger(3).updateAndGet(add.andThen(mul));
	int j = new AtomicInteger(3).updateAndGet(add.compose(mul));
	System.out.println(i);// 36
        System.out.println(j);// 18
}
   以上示例中,我构造了一个加法器(operand + operand)和一个乘法器(operand * operand)的IntUnaryOperator两个实现类,然后执行初始值为3的AtomicInteger的updateAndGet方法,该方法的参数是add.andThen(mul),执行结果是36,  为何结果是36呢?这里可以简单的这样分析,updateAndGet的参数的applyAsInt实现就是add.andThen(mul)的返回值IntUnaryOperator实例的实现,而Lambda表达式可以看作是一种匿名内部类,所以add.andThen(mul)的返回值IntUnaryOperator实例的实现其实就是andThen方法体,所以执行的过程就是执行andThen方法体的过程,andThen的方法体after.applyAsInt(applyAsInt(t))先执行add自身的实现:3+3=6,然后执行参数after即mul的实现:6*6 = 36;所以结果就出来了。 同理,如果将andThen换成compose,那么就先算mul:3 * 3= 9;然后add:9 + 9= 18;
注意,这里虽然说Lambda表达式可以看作是一种匿名内部类,但它和匿名内部类最大的区别在于this指针的词法作用域,匿名内部类的this指向的是匿名内部类本身,而Lambda表达式所类比的匿名内部类的this指针指向的确是外部类实例,所以当你真的将Lambda表达式转换为匿名内部类之后,由于this指针的不确定可能会非常难以理解,例如,我们如果把andThen的方法体转换为匿名内部类:
@Override
public IntUnaryOperator andThen(IntUnaryOperator after) {
	
	//return (int t) -> after.applyAsInt(applyAsInt(t));
	return new IntUnaryOperator(){

		@Override
		public int applyAsInt(int t) {
			return after.applyAsInt(applyAsInt(t));
		}
		
	};
}
    如果不知道此时this指针其实指向的是外部类add实例,那么你将会感到非常困惑,因为匿名内部类中的applyAsInt方法实现又执行了applyAsInt,如果按照Java匿名内部类的语义,这里肯定是递归到内存溢出的死循环调用了,所以我们要明白Lambda表达式虽然可以理解为匿名内部类,但是对this指针的含义已经发生变化而不能按照原来的语义进行解读。更多函数式编程 参考
3. 函数式外更新 
与函数式自更新不同,函数式外更新可以借助外部参数进行更加复杂的逻辑运算,而不仅限于传统的加减运算,这类方法是int getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) 和
int accumulateAndGet(int x,IntBinaryOperator accumulatorFunction)分别对应了进行相应复杂操作之后返回是旧值或新值。下面是两个方法的源码:
public final int getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) {
	int prev, next;
	do {
		prev = get();
		next = accumulatorFunction.applyAsInt(prev, x);
	} while (!compareAndSet(prev, next));
	return prev;
}

public final int accumulateAndGet(int x,IntBinaryOperator accumulatorFunction) {
	int prev, next;
	do {
		prev = get();
		next = accumulatorFunction.applyAsInt(prev, x);
	} while (!compareAndSet(prev, next));
	return next;
}
    从以上源码可以看出,它接受一个外部变量x参与运算,具体的运算逻辑由第二个IntBinaryOperator类型的参数的applyAsInt方法实现,我们接着看IntBinaryOperator的源码:
@FunctionalInterface
public interface IntBinaryOperator {

    int applyAsInt(int left, int right);
}
    IntBinaryOperator是很简单的接口,有且仅有一个实现真正的运算逻辑的方法接口方法,在AtomicInteger的这两个方法中就是以旧值和传入的参数(x)进行实现运算逻辑。我们以实现x的y次方为例:
public static void main(String[] args) {
	
	IntBinaryOperator pow = new IntBinaryOperator(){

		@Override
		public int applyAsInt(int left, int right) {
			return (int) Math.pow(left, right);
		}
		
	};

	System.out.println(new AtomicInteger(2).accumulateAndGet(10, pow)); //1024
}
   在上例中,我们使用IntBinaryOperator实现了原子的更新为传入值的多少次方,最后输出的结果就是2的10次方1024.
当然,我们可以将函数式自更新和函数式外更新结合起来一起使用,即同时使用IntUnaryOperator和IntBinaryOperator实现更加复杂的原子更新逻辑。在AtomicLong中也对应了这四种类型的原子更新操作,只不过AtomicLong操作的是long类型的基本类型和LongUnaryOperator、LongBinaryOperator。
关于 AtomicBoolean,其实内部也是使用一个volatile修饰的int类型的变量来 表示布尔状态的,1表示true,0表示false。

AtomicReference

AtomicInteger和AtomicLong是基本类型,我们当然可以进行加减乘除的四则运算,作为引用类型的原子类AtomicReference当然不能进行这样的操作,而只能对引用指向的内存地址进行修改(即使其指向新的对象或者仅仅修改成员属性),那么它又是怎么进行原子更新的呢?
除了在序章中提到的 set、lazySet、compareAndSet、weakCompareAndSet方法之外,AtomicReference也提供了如下几类原子更新操作:
1. 直接更新
public final V getAndSet(V newValue) {
    return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}
    即直接使用CAS+volatile关键字实现直接更新引用类型的值,返回更新之前的旧值。当有多个线程需要对同一个引用类型的变量的多个成员属性进行更新时,我们无法保证每个线程在更新所有字段时是作为一个整体进行原子更新的,即更新之后可能某些字段是一些线程更新的,有些字段又是其他线程更新的,如果我们想把所有字段看成一个整体进行原子的更新就可以使用AtomicReference的getAndSet方法。
2. 函数式自更新

与AtomicInteger的函数式自更新类似,在不借助外部变量的情况下,仅根据引用变量自身进行逻辑运算并更新,它对应的方法分别是getAndUpdate/updateAndGet依然只是返回值是旧值或新值的区别:

public final V getAndUpdate(UnaryOperator<V> updateFunction) {
	V prev, next;
	do {
		prev = get();
		next = updateFunction.apply(prev);
	} while (!compareAndSet(prev, next));
	return prev;
}

public final V updateAndGet(UnaryOperator<V> updateFunction) {
	V prev, next;
	do {
		prev = get();
		next = updateFunction.apply(prev);
	} while (!compareAndSet(prev, next));
	return next;
}

    UnaryOperator依然是一个接口类,apply是待实现的接口方法,它的其他方法与IntUnaryOperator非常类似也是compose、andThen两个对执行顺序控制的方法,这里就不再提供源码。

    

public static void main(String[] args) {
	Person p0 = new Person(10, "Tom"); //age, name

	AtomicReference<Person> ar = new AtomicReference<Person>(p0);
	
	UnaryOperator<Person> a = new UnaryOperator<Person>(){

		@Override
		public Person apply(Person t) {
			
			if(t.getAge() == 10){
				t.setAge(11);
				t.setName("Tom11");
			}
			return t;
		}
		
	};
	System.out.println(ar.updateAndGet(a).toString());
}
   这里我只简单的举例为当初始年龄为10的时候,更新age为11,name为Tom11,其实可以实现更复杂的运算逻辑,至于能不能运用compose、andThen这两个方法,我觉得应该不能,因为这两个方法返回的是Function的实例无法向子类UnaryOperator进行转换。

 

 

3. 函数式外更新
与AtomicInteger的函数式外更新类似, AtomicReference也能借助外部参数进行复杂的逻辑运算并更新原子变量,它对应的方法分别是getAndAccumulate/accumulateAndGet依然只是返回值是旧值或新值的区别:
public final V getAndAccumulate(V x,BinaryOperator<V> accumulatorFunction) {
	V prev, next;
	do {
		prev = get();
		next = accumulatorFunction.apply(prev, x);
	} while (!compareAndSet(prev, next));
	return prev;
}

public final V accumulateAndGet(V x,BinaryOperator<V> accumulatorFunction) {
	V prev, next;
	do {
		prev = get();
		next = accumulatorFunction.apply(prev, x);
	} while (!compareAndSet(prev, next));
	return next;
}
    BinaryOperator接口类需要实现的方法是其父接口BiFunction中的接口方法apply,除了从父接口BiFunction中继承了andThen方法之外,它自身还有两个静态方法minBy和maxBy分别比较两个对象返回最小值和最大值:
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
	Objects.requireNonNull(comparator);
	return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}

public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
	Objects.requireNonNull(comparator);
	return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
 相关的示例如下:
public static void main(String[] args) {
	Person p0 = new Person(10, "Tom");
	Person p1 = new Person(12, "Tom12");
	AtomicReference<Person> ar = new AtomicReference<Person>(p0);
	AtomicReference<Person> ar1 = new AtomicReference<Person>(p0);
	
	BinaryOperator<Person> b = new BinaryOperator<Person>(){

		@Override
		public Person apply(Person t, Person u) {
			if(t.getAge() == 10){
				return new Person(11, "Tom11");
			}
			return t;
		}
	};
	
	System.out.println(ar.accumulateAndGet(p1, b)); //ar更新为新的Person对象:age为11,name为Tome11
	
	System.out.println(ar.accumulateAndGet(p1, BinaryOperator.maxBy(new Comparator<Person>() {

		@Override
		public int compare(Person o1, Person o2) {
			return o1.getAge() - o2.getAge();
		}
		
	}))); //设置并返回年龄最大的对象P1:age为12,name为Tome12,其实就是如果p1比ar对应的Person实例大才更新为新值p1,否则还是自己。
	
	System.out.println(ar1.accumulateAndGet(p1, BinaryOperator.minBy(new Comparator<Person>() {

		@Override
		public int compare(Person o1, Person o2) {
			return o1.getAge() - o2.getAge();
		}
		
	})));//设置并返回年龄最小的对象P0:age为10,name为Tom,其实就是如果p1比ar1对应的p0小才更新为新值p1,否则还是自己。
}
 按我的理解,AtomicReference的getAndAccumulate/accumulateAndGet方法依然无法利用BinaryOperator从父接口中继承的andThen方法,因为andThen方法返回的是BiFunction类型的实例,无法向子类BinaryOperator进行转换。
如果要了解更底层的细节可以参数http://brokendreams.iteye.com/blog/2250109 
 
分享到:
评论

相关推荐

    XML与数据库数据的交互技术研究

    这一步骤通常涉及到规范化设计,确保数据结构的合理性。 #### 数据同步与交互 在实现了XML与数据库之间的转换后,还需要关注数据的安全性、一致性和并发控制等问题。例如,在进行数据同步时,可以采用以下策略: ...

    Metal shading 语言指南

    而MetalDataTypes,即Metal数据类型,包括了标量数据类型、向量和矩阵数据类型,提供了对基本数据和复杂数据结构(如向量、矩阵)的支持。文档还涵盖了向量和矩阵的构造器的使用方法,以及原子数据类型,用于保证多...

    17春福师《计算机体系结构》在线作业一.docx

    在IBM 370系统中,"比较与交换"指令对于操作系统实现多进程共用公用区的管理尤其有效,因为它能够原子地比较和更新内存中的值,这对于同步和资源管理至关重要。 在多处理机上,如果存在先写后读的数据相关,这意味...

    深入java内存模型

    8. **CAS(Compare and Swap)与无锁编程**:介绍无锁数据结构和算法,以及基于CAS操作的原子类如何实现线程安全。 9. **JVM调优实践**:提供实际的JVM调优案例,包括使用JConsole、VisualVM等工具进行性能监控,...

    mysql8中文参考手册-latest200708.zip

    MySQL 8.0的复制功能使得数据可以从一个服务器同步到多个从服务器,实现高可用性和灾难恢复。此外,InnoDB Cluster是MySQL原生的分布式集群解决方案,提供自动故障切换和数据冗余。 九、JSON支持 MySQL 8.0增加了对...

    OpenMP用户指南.pdf

    OpenMP提供了多种同步构造,包括master构造(指示只有主线程执行)、critical构造(代码段一次只允许一个线程执行)、barrier构造(线程执行到此构造时必须等待其他线程到达)、atomic构造(保证原子操作)和flush...

    福师春秋学期计算机体系结构在线作业一样本.doc

    3. 公用区管理:在IBM370系统中,"比较与互换"指令被用于多进程共用公用区的管理,它允许原子地比较内存中的值并根据比较结果进行更新,这是实现同步和互斥的关键指令。 4. 多处理器程序执行:如果两个程序段之间...

    最热门的Java 面试题汇总

    56. **处理频繁FullGC**:调整JVM参数、减少对象生命周期、优化数据结构、使用更高效的GC算法。 57. **双亲委派模型**:类加载器从Bootstrap ClassLoader开始,向上委托,直到找到合适的加载器。 58. **类加载过程...

    轻松搞定SQL Server 2000程序设计

    - **串联删除**:在相关表中同步删除数据。 - **Table数据类型**:用于存储表类型的值。 - **INSTEAD OF触发器**:在某些操作发生时执行替代操作。 - **自定义函数**:扩展SQL语言的功能。 #### 九、实践指南 - **...

    底层篇.pdf

    Java内存模型规定了线程间的数据可见性和原子性,遵循happens-before原则,以保证程序的正确执行。synchronized和volatile关键字用于实现线程安全,final关键字确保变量的不可变性。类加载机制由ClassLoader实现,...

    分布式系统领域教程pdf

    3.4.1 标量逻辑时钟 3.4.2 扩展 3.4.3 有效实现 3.4.4 物理时钟 3.5 应用 3.5.1 一个全序应用:分布式互斥 3.5.2 一个逻辑向量时钟应用:消息的 排序 3.6 分布式控制算法的分类 3.7 分布式算法的复杂性 第4...

    分布式系统设计 [美]jie wu著 高传善 译

    3.4.1 标量逻辑时钟 3.4.2 扩展 3.4.3 有效实现 3.4.4 物理时钟 3.5 应用 3.5.1 一个全序应用:分布式互斥 3.5.2 一个逻辑向量时钟应用:消息的 排序 3.6 分布式控制算法的分类 3.7 分布式算法的复杂性 第4...

    ADO.NET EntityFramework 完整版教程(从初级到高级)

    - **事务**:使用事务确保数据操作的原子性和一致性。 #### 六、高级特性 - **支持复杂类型**:允许在实体中嵌入其他非标量类型,例如地址或联系信息等。 - **存储过程支持**:EF 支持直接调用数据库中的存储过程,...

    SQL Server精华技巧集

    - 学习如何配置Always On Availability Groups,实现跨多个服务器的数据同步。 10. **错误处理与日志管理** - 使用`TRY...CATCH`结构进行异常处理,编写健壮的T-SQL代码。 - 管理SQL Server的日志文件,包括错误...

    优化方法

    6. **数据结构的选择**:根据实际需求选择合适的数据结构,如ArrayList与LinkedList在不同场景下的性能差异。 五、类加载优化 1. **类加载器**:Java的双亲委托模型保证了类的唯一性,理解类加载机制有助于优化类...

    Rust思维导图.pdf

    #### 三、数据结构 **3.1 基本类型** - **标量类型:** 包括整数(如 `i32`)、浮点数(如 `f64`)、字符(`char`)和布尔值(`bool`)。 - **复合类型:** 包括元组(Tuple)和数组(Array),它们可以存储多个...

    Java中性能优化的35种方法汇总

    10. 使用适当的数据结构:选择适合业务场景的数据结构,如使用`HashMap`而非`TreeMap`,如果对排序没有要求,可以减少不必要的比较和排序操作。 11. 避免过度使用同步:过多的同步会成为性能瓶颈,应尽量减少同步块...

    73页 张舒——CUDA基本介绍

    CUDA不仅提供了一套完整的软件栈,包括编译器、库函数和开发工具,还定义了硬件结构,从而使得开发者能够高效地利用GPU进行高性能计算。 #### GPU的优势 - **强大的处理能力**:现代GPU的浮点运算能力接近或超过1...

    自动化英语词汇自动化可以加入默默背.txt

    不同时发生的,通常指不同部件之间没有固定的同步关系。 - **attenuation**:衰减。信号传输过程中强度的减少。 - **attenuator**:衰减器。一种用于减少信号强度的设备。 - **augment**:增强。在自动化系统中,...

Global site tag (gtag.js) - Google Analytics