`
春花秋月何时了
  • 浏览: 39629 次
  • 性别: 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 
 
分享到:
评论

相关推荐

    标量源消失的标量解和标量标度的分类

    我们推导了d + 2维中最小耦合的爱因斯坦-麦克斯韦-标量重力的精确Brane解,并且标量势消失了,我们证明了这些解与Lifshitz时空是共形的,其双重QFT具有超尺度违规的特征。 这些解决方案与AdS麸和指数势所产生的畴壁...

    深入JVM系列-逃逸分析、同步省略、栈上分配、标量替换1

    深入JVM系列-逃逸分析、同步省略、栈上分配、标量替换深入JVM系列-逃逸分析、同步省略、栈上分配、标量替换逃逸分析方法逃逸线程逃逸优化同步省略同步省略对性能影

    特殊标量理论的红外结构

    例外的理论是一组单参数标量场理论,在S矩阵元素中具有(增强的)... 此外,我们为特殊伽利略的扩展理论提出了一个拉格朗日法,它具有丰富的颗粒含量,包括双联标量,南布-戈德斯通玻色子和伽利略,以及其他风味结构。

    伪标量与标量的弱电重生和暗物质

    我们研究了铁伪暗物质场景中的电弱重生,其中伪伪标量是希格斯门户中的介体。 讨论了在我们扩展的标准模型中考虑到(伪)标量在热有效电势中的作用后,电弱相变变成一阶的。 施加来自WMAP / Planck的文物密度约束和...

    麦克斯韦-标量模型和爱因斯坦-麦克斯韦-标量模型中的一类孤子

    最近,已经建立了关于爱因斯坦-麦克斯韦-标量(EMS)模型中孤子解存在性的不成立定理(Herdeiro和Oliveira在类量子引力36(10):105015,2019中)。 在这里,我们讨论如何通过实数,规范的标量场和电磁场之间的特定...

    三维标量数据的可视化.docx

    三维标量数据的可视化.docx

    Python的数据结构.pdf

    1、集合是独⽴于标量,序列和映射之外的特殊数据结构,它⽀持数学理论的各种集合的运算。它的存在使得⽤程序代码实现数学理论变得 ⽅便。 2、序列是Python中最为基础的内建类型。它分为七种类型:列表、字符串、...

    标量乘法快速算法

    标量乘法的快速算法,做嵌入式工作的可以看一看

    轻标量介子为四夸克

    我们为JPC = 0 ++的基态标量四夸克提供了四夸克Betheâ€:trade_mark:Salpeter方程的数值解。 我们发现,四体方程在Bethe'Salpeter振幅中动态生成伪标量介子极。 最终的四夸克是真正的四夸克状态,由伪标量介子—...

    早期LHC运行2数据中的伪标量衰变成光子对

    在本文中,我们探讨了伪标量共振的可能性,以解决在ATLAS和CMS处观察到的750 GeV双光子过量的问题。 我们从低能量的角度分析了所需的成分,以获得足够大的双光子速率来解释信号,同时避免了其他通道的限制。 此外,...

    伪标量胶球及其第一个激发态衰减为标量和伪标量介子及其第一个激发态

    我们通过构造一个相互作用的拉格朗日函数来扩展伪标量胶球及其第一个激发态的研究,该相互作用将伪标量胶球的两体和三体衰变JPC = 0- +分解为(伪)标量和激发(伪) 介子介子,以及通过构造另外两个不同的手性拉格...

    论文研究 - 启发性标量NS-WD二元的引力希格斯机制

    我们研究了吸气的标量中子星白矮星(NS-WD)双星中的引力希格斯机制,其动力学由标量张量理论描述。 由于NS和WD的结合能不同,标量化NS-WD系统的轨道衰减实际上会产生偶极引力标量辐射,除了张量引力波,这打破了...

    再次探讨伪标量介子和胶球混合以及寻找伪标量胶球状态的关键问题

    我们回顾了轴向矢量异常引入的伪标量介子和胶球的混合机制。 我们证明,如果所有参数都受到合理约束,则伪标量胶球的物理质量不希望低于1.8 GeV。 这个结论一方面可以容纳由点阵QCD计算的拟标量胶球质量,另一方面与...

    用标量共振敲开新物理学的门

    我们推测通过ATLAS和CMS实验使用第一个13 TeV数据观察到的在双光子共振搜索中,在≥750 GeV处最近过量的起源。 它被解释为在胶子聚变中产生的新的标量共振,并衰减为光子,这与来自8 TeV LHC运行的所有相关排除边界...

    具有U(1)规范相互作用的DAMPE数据的标量暗物质解释

    我们利用标量暗物质(DM)模型通过χχ→Z′Z′→ℓℓ¯ℓ′ℓ′′来解释DAMPE峰,并具有附加的无异常量度U(1)族对称性,其中χ,Z′ 分别表示标量DM,新规范玻色子和ℓ(')= e,μ,τ,其中mχ〜mZ'〜2×1.5...

    标量与簇的应用

    标量与簇的应用

    标量衍射理论的计算机模拟

    标量衍射理论的计算机模拟,matlab,模拟衍射

    引力的扩展标量张量理论

    我们研究了Langlois和Noui最近引入的具有一致意义的新的标量-张量引力理论,并提出了可能有趣的宇宙学应用。 我们得出存在主要约束条件的条件,该条件阻止了与高阶运动方程式关联的其他危险模式的传播。 然后,我们...

    介子标量形式因子和轻夸克质量的模型无关约束

    我们在Meiman-Okubo框架中研究介子标量形式因子,实现了低于无弹性KK阈值的相位,这是根据Watson定理从ππ标量等标量相移δ00得知的。 标量相关器的扰动QCD展开和相移δ00的最新知识用作输入。 无需对高于非弹性...

Global site tag (gtag.js) - Google Analytics