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

引言

通过原子类序章我们知道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关键字的方式实现。

 1 public final int getAndIncrement() {  
 2         return unsafe.getAndAddInt(this, valueOffset, 1);  
 3 }  
 4   
 5 //Unsafe:  
 6 public final int getAndAddInt(Object o, long offset, int delta) {  
 7         int v;  
 8         do {  
 9             v = getIntVolatile(o, offset);  
10         } while (!compareAndSwapInt(o, offset, v, v + delta));  
11         return v;  
12 }  
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) 分别对应了获取旧值并进行复杂的函数式运算和先进行函数式运算更新实例的值然后返回新值这两种操作类型,下面是这两个方法的源码:
 1 public final int getAndUpdate(IntUnaryOperator updateFunction) {  
 2         int prev, next;  
 3         do {  
 4             prev = get();  
 5             next = updateFunction.applyAsInt(prev);  
 6         } while (!compareAndSet(prev, next));  
 7         return prev;  
 8 }  
 9   
10 public final int updateAndGet(IntUnaryOperator updateFunction) {  
11         int prev, next;  
12         do {  
13             prev = get();  
14             next = updateFunction.applyAsInt(prev);  
15         } while (!compareAndSet(prev, next));  
16         return next;  
17 }  

从表面上看,以上两个方法看不出函数式编程的影子,只是传入了一个IntUnaryOperator变量,执行了它的applyAsInt方法,我们接着看IntUnaryOperator的源码:

 1 @FunctionalInterface  
 2 public interface IntUnaryOperator {  
 3   
 4 int applyAsInt(int operand);  
 5   
 6 default IntUnaryOperator compose(IntUnaryOperator before) {  
 7     Objects.requireNonNull(before);  
 8     return (int v) -> applyAsInt(before.applyAsInt(v));  
 9 }  
10   
11 default IntUnaryOperator andThen(IntUnaryOperator after) {  
12     Objects.requireNonNull(after);  
13     return (int t) -> after.applyAsInt(applyAsInt(t));  
14 }  
15   
16 static IntUnaryOperator identity() {  
17     return t -> t;  
18 }  

 

IntUnaryOperator是一个接口,需要我们实现的方法正是applyAsInt方法,而且它的另外两个方法compose、andThen也直接调用该接口方法,这两个方法分别实现了前置和后置处理,详细的说就是,compose方法会以传入的IntUnaryOperator实例参数的applyAsInt执行结果为输入去执行自身的applyAsInt方法,也就是先执行参数的IntUnaryOperator实现,然后才执行自己的实现;而andThen先执行自己的实现,最后以其返回值作为输入执行参数的IntUnaryOperator实现,这两个方法不但实现了流式的函数式编程效果,而且还直接使用了Java8引入的Lambda表达式(->),利用IntUnaryOperator接口实现我们就可以实现复杂的自更新逻辑。下面以一个例子来说明IntUnaryOperator的简单使用方式:
 
 1 public static void main(String[] args) {  
 2     IntUnaryOperator add = new IntUnaryOperator(){  
 3   
 4         @Override  
 5         public int applyAsInt(int operand) {  
 6             return operand + operand;  
 7         }  
 8     };  
 9       
10     IntUnaryOperator mul = new IntUnaryOperator(){  
11   
12         @Override  
13         public int applyAsInt(int operand) {  
14             return operand * operand;  
15         }  
16     };  
17       
18     int i = new AtomicInteger(3).updateAndGet(add.andThen(mul));  
19     int j = new AtomicInteger(3).updateAndGet(add.compose(mul));  
20     System.out.println(i);// 36  
21         System.out.println(j);// 18  
22 }

 

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

从以上源码可以看出,它接受一个外部变量x参与运算,具体的运算逻辑由第二个IntBinaryOperator类型的参数的applyAsInt方法实现,我们接着看IntBinaryOperator的源码:

1 @FunctionalInterface  
2 public interface IntBinaryOperator {  
3   
4     int applyAsInt(int left, int right);  
5 }  

IntBinaryOperator是很简单的接口,有且仅有一个实现真正的运算逻辑的方法接口方法,在AtomicInteger的这两个方法中就是以旧值和传入的参数(x)进行实现运算逻辑。我们以实现x的y次方为例:

 1 public static void main(String[] args) {  
 2       
 3     IntBinaryOperator pow = new IntBinaryOperator(){  
 4   
 5         @Override  
 6         public int applyAsInt(int left, int right) {  
 7             return (int) Math.pow(left, right);  
 8         }  
 9           
10     };  
11   
12     System.out.println(new AtomicInteger(2).accumulateAndGet(10, pow)); //1024  
13 }  
在上例中,我们使用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. 直接更新
1 public final V getAndSet(V newValue) {  
2     return (V)unsafe.getAndSetObject(this, valueOffset, newValue);  
3 } 
即直接使用CAS+volatile关键字实现直接更新引用类型的值,返回更新之前的旧值。当有多个线程需要对同一个引用类型的变量的多个成员属性进行更新时,我们无法保证每个线程在更新所有字段时是作为一个整体进行原子更新的,即更新之后可能某些字段是一些线程更新的,有些字段又是其他线程更新的,如果我们想把所有字段看成一个整体进行原子的更新就可以使用AtomicReference的getAndSet方法。
2. 函数式自更新

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

 1 public final V getAndUpdate(UnaryOperator<V> updateFunction) {  
 2     V prev, next;  
 3     do {  
 4         prev = get();  
 5         next = updateFunction.apply(prev);  
 6     } while (!compareAndSet(prev, next));  
 7     return prev;  
 8 }  
 9   
10 public final V updateAndGet(UnaryOperator<V> updateFunction) {  
11     V prev, next;  
12     do {  
13         prev = get();  
14         next = updateFunction.apply(prev);  
15     } while (!compareAndSet(prev, next));  
16     return next;  
17 }  

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

 1 public static void main(String[] args) {  
 2     Person p0 = new Person(10, "Tom"); //age, name  
 3   
 4     AtomicReference<Person> ar = new AtomicReference<Person>(p0);  
 5       
 6     UnaryOperator<Person> a = new UnaryOperator<Person>(){  
 7   
 8         @Override  
 9         public Person apply(Person t) {  
10               
11             if(t.getAge() == 10){  
12                 t.setAge(11);  
13                 t.setName("Tom11");  
14             }  
15             return t;  
16         }  
17           
18     };  
19     System.out.println(ar.updateAndGet(a).toString());  
20 }  
这里我只简单的举例为当初始年龄为10的时候,更新age为11,name为Tom11,其实可以实现更复杂的运算逻辑,至于能不能运用compose、andThen这两个方法,我觉得应该不能,因为这两个方法返回的是Function的实例无法向子类UnaryOperator进行转换。
3. 函数式外更新
与AtomicInteger的函数式外更新类似, AtomicReference也能借助外部参数进行复杂的逻辑运算并更新原子变量,它对应的方法分别是getAndAccumulate/accumulateAndGet依然只是返回值是旧值或新值的区别:
 1 public final V getAndAccumulate(V x,BinaryOperator<V> accumulatorFunction) {  
 2     V prev, next;  
 3     do {  
 4         prev = get();  
 5         next = accumulatorFunction.apply(prev, x);  
 6     } while (!compareAndSet(prev, next));  
 7     return prev;  
 8 }  
 9   
10 public final V accumulateAndGet(V x,BinaryOperator<V> accumulatorFunction) {  
11     V prev, next;  
12     do {  
13         prev = get();  
14         next = accumulatorFunction.apply(prev, x);  
15     } while (!compareAndSet(prev, next));  
16     return next;  
17 }  

BinaryOperator接口类需要实现的方法是其父接口BiFunction中的接口方法apply,除了从父接口BiFunction中继承了andThen方法之外,它自身还有两个静态方法minBy和maxBy分别比较两个对象返回最小值和最大值:

1 public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {  
2     Objects.requireNonNull(comparator);  
3     return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;  
4 }  
5   
6 public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {  
7     Objects.requireNonNull(comparator);  
8     return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;  
9 } 

相关的示例如下:

 1 public static void main(String[] args) {  
 2     Person p0 = new Person(10, "Tom");  
 3     Person p1 = new Person(12, "Tom12");  
 4     AtomicReference<Person> ar = new AtomicReference<Person>(p0);  
 5     AtomicReference<Person> ar1 = new AtomicReference<Person>(p0);  
 6       
 7     BinaryOperator<Person> b = new BinaryOperator<Person>(){  
 8   
 9         @Override  
10         public Person apply(Person t, Person u) {  
11             if(t.getAge() == 10){  
12                 return new Person(11, "Tom11");  
13             }  
14             return t;  
15         }  
16     };  
17       
18     System.out.println(ar.accumulateAndGet(p1, b)); //ar更新为新的Person对象:age为11,name为Tome11  
19       
20     System.out.println(ar.accumulateAndGet(p1, BinaryOperator.maxBy(new Comparator<Person>() {  
21   
22         @Override  
23         public int compare(Person o1, Person o2) {  
24             return o1.getAge() - o2.getAge();  
25         }  
26           
27     }))); //设置并返回年龄最大的对象P1:age为12,name为Tome12,其实就是如果p1比ar对应的Person实例大才更新为新值p1,否则还是自己。  
28       
29     System.out.println(ar1.accumulateAndGet(p1, BinaryOperator.minBy(new Comparator<Person>() {  
30   
31         @Override  
32         public int compare(Person o1, Person o2) {  
33             return o1.getAge() - o2.getAge();  
34         }  
35           
36     })));//设置并返回年龄最小的对象P0:age为10,name为Tom,其实就是如果p1比ar1对应的p0小才更新为新值p1,否则还是自己。  
37 }  
按我的理解,AtomicReference的getAndAccumulate/accumulateAndGet方法依然无法利用BinaryOperator从父接口中继承的andThen方法,因为andThen方法返回的是BiFunction类型的实例,无法向子类BinaryOperator进行转换。
如果要了解更底层的细节可以参数http://brokendreams.iteye.com/blog/2250109 
 
 
 
posted @ 2021-05-11 16:43  莫待樱开春来踏雪觅芳踪  阅读(159)  评论(0)    收藏  举报