【Java并发专题之七】juc-locks之原子框架
环境
jdk version:jdk1.8.0_171
原子包不需要使用同步器,而是单纯的使用CAS+自旋实现同步效果,CAS主要通过Unsafe来实现。
一、基本类型(AtomicInteger, AtomicLong, AtomicBoolean)
AtomicInteger是Integer类型的线程安全原子类,可以在应用程序中以原子的方式更新int值。
AtomicLong是Long类型的线程安全原子类,可以在应用程序中以原子的方式更新long值。
AtomicBoolean内部维护了一个int值来表示boolean,1表示true,0表示false,内部的方法操作类似AtomicInteger对int的操作。
示例:
package test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { int intCount = 0; AtomicInteger atomicCount = new AtomicInteger(0); /** * @Desc: 自增操作 */ public void m1() { for (int i = 0; i < 10000; i++) { intCount ++;//不是原子的 atomicCount.incrementAndGet();//原子操作 } } public static void main(String[] args) throws InterruptedException { AtomicIntegerTest test = new AtomicIntegerTest(); //创建线程 List<Thread> tl= new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { tl.add(new Thread(test::m1, "Thread-" + i)); } //启动 tl.forEach((o) -> o.start()); //join tl.forEach((o) -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); //输出结果 System.out.println(test.intCount);//96378 每次结果不一样 System.out.println(test.atomicCount.get());//100000 } }
二、引用类型(AtomicReference, AtomicStampedReference,AtomicMarkableReference)
1、AtomicReference提供了以无锁方式访问共享资源并保证线程安全的能力。
package test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; public class AtomicRefTest { AtomicReference<Integer> ref = new AtomicReference<>(new Integer(1000)); public void oper() { for (;;) { //自旋操作 Integer oldV = ref.get(); if (ref.compareAndSet(oldV, oldV + 1)) // CAS操作 break; } } public static void main(String[] args) throws InterruptedException { AtomicRefTest test = new AtomicRefTest(); List<Thread> list = new ArrayList<Thread>(); for (int i = 0; i < 1000; i++) { Thread t = new Thread(test::oper, "Thread-" + i); list.add(t); } list.forEach((t)->t.start()); list.forEach((t)->{ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(test.ref.get()); // 打印2000 } }
2、AtomicStampedReference
用来解决ABA问题:假如一个值原来是A,变成了B,又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了。
解决方案就是对于每一次变化值标上版本号,比如A-B-A,加版本号:1A - 2B - 3A。
示例:
package test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; public class AbaTest { private static AtomicInteger atomicInt = new AtomicInteger(100); private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0); public void intrun1(){ atomicInt.compareAndSet(100, 101); atomicInt.compareAndSet(101, 100); } public void intrun2(){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } boolean c3 = atomicInt.compareAndSet(100, 101); System.out.println("intrun2 c3="+c3); // true } public void refrun1(){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); } public void refrun2(){ int stamp = atomicStampedRef.getStamp(); try { Thread.sleep(2); } catch (InterruptedException e) { } boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1); System.out.println("refrun2 c3="+c3); // false } public static void main(String[] args) throws InterruptedException { AbaTest test = new AbaTest(); List<Thread> lt1 = new ArrayList<Thread>(); List<Thread> lt2 = new ArrayList<Thread>(); lt1.add(new Thread(test::intrun1)); lt1.add(new Thread(test::intrun2)); lt1.forEach((t)->t.start()); lt1.forEach((t)->{ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); lt2.add(new Thread(test::refrun1)); lt2.add(new Thread(test::refrun2)); lt2.forEach((t)->t.start()); /***************************************AtomicStampedReference常见用法******************************************/ AtomicStampedReference<String> atomic = new AtomicStampedReference<String>("aaa",1); //获取元素值(pair.reference) System.out.println(atomic.getReference());//"aaa" //获取版本号(pair.stamp) System.out.println(atomic.getStamp());//1 //不修改元素值,只修改版本, boolean isOk = atomic.attemptStamp("aaa",atomic.getStamp() + 1); System.out.println(isOk);//true System.out.println(atomic.getStamp());//2 //修改元素值和版本号 boolean isOk1 = atomic.compareAndSet(atomic.getReference(),"bbb",atomic.getStamp(),atomic.getStamp() + 1); System.out.println(isOk1);//true System.out.println(atomic.getReference());//"bbb" System.out.println(atomic.getStamp());//3 //与compareAndSet一样,因为内部直接调用的compareAndSet方法 boolean isOk2 = atomic.weakCompareAndSet(atomic.getReference(),"ccc",atomic.getStamp(),atomic.getStamp() + 1); System.out.println(isOk2);//true System.out.println(atomic.getReference());//"ccc" System.out.println(atomic.getStamp());//4 //获得版本号和元素值 int [] stamp = new int[1]; String reFenence = atomic.get(stamp); System.out.println("stamp:"+stamp[0]);//stamp:4 System.out.println("reFenence:"+reFenence);//reFenence:ccc } }
3、AtomicMarkableReference
通过构造器看出,AtomicMarkableReference与AtomicStampedReference的唯一区别就是不再用int标识引用,而是使用boolean变量表示引用变量是否被更改过。
AtomicMarkableReference对于那些不关心引用变化过程,只关心引用变量是否变化过。
示例:
package test; import java.util.concurrent.atomic.AtomicMarkableReference; public class AtomicMarkableReferenceTest { AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<Integer>(0, false); public void run() { String name = Thread.currentThread().getName(); System.out.println("线程" + name + "读到原子标记的初始化值为:" + amr.isMarked()); //boolean compareAndSet(V expectedReference, V newReference,boolean excpectedMark,boolean newMark) //如果当前引用Reference 和 expectedReference相同,并且当前标记mark值和期望mark值相同,则原子更新引用和标记为新值newReference 和 newMark if (amr.compareAndSet(0, 1, false, true)) { System.out.println("线程" + name + "操作的原子当前值为:" + amr.isMarked()); System.out.println("线程" + name + "正在将原子标记的值由0设置为1"); } else { System.out.println("线程" + name + "原子操作没有成功"); } } public static void main(String[] args) { AtomicMarkableReferenceTest test = new AtomicMarkableReferenceTest(); Thread ta = new Thread(test::run,"A"); Thread tb = new Thread(test::run,"B"); ta.start(); tb.start(); /* 线程A读到原子标记的初始化值为:false 线程A操作的原子当前值为:true 线程A正在将原子标记的值由0设置为1 线程B读到原子标记的初始化值为:true 线程B原子操作没有成功 */ } }
三、数组类型(AtomicIntegerArray, AtomicLongArray,AtomicReferenceArray)
原子数组类型其实可以看成原子类型组成的数组,原子数组并不是说可以让线程以原子方式一次性地操作数组中所有元素,而是指对于数组中的每个元素,可以以原子方式进行操作。
package test; import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicIntegerArrayTest { public static void main(String[] args){ AtomicIntegerArray arr = new AtomicIntegerArray(new int[]{1,2,3}); System.out.println(arr.get(1));//2 System.out.println(arr.compareAndSet(1,2,3));//true System.out.println(arr.get(1));//3 } }
四、对象的属性修改类型(AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater)
以一种线程安全的方式操作非线程安全对象的某些字段
示例:
package test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class Account { private volatile int money = 0; private static final AtomicIntegerFieldUpdater<Account> updater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money"); // 引入AtomicIntegerFieldUpdater Account(int initial) { this.money = initial; } public void increMoney() { // 通过AtomicIntegerFieldUpdater操作字段 updater.incrementAndGet(this); } public int getMoney() { return money; } @Override public String toString() { return "Account{" + "money=" + money + '}'; } public static void main(String[] args){ Account account = new Account(0); List<Thread> list = new ArrayList<Thread>(); for (int i = 0; i < 100; i++) { list.add(new Thread(account::increMoney,"Thread-"+i)); } list.forEach((t)->t.start()); list.forEach((t)->{ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(account.toString());//Account{money=100} } }
AtomicReferenceFieldUpdater示例:
package test; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.BinaryOperator; public class AtomicReferenceFieldUpdaterDeo { public static void main(String args[]) { Person person = new Person(1001, "whisper"); AtomicReferenceFieldUpdater<Person, String> updater = AtomicReferenceFieldUpdater.newUpdater(Person.class, String.class, "name"); //测试1 boolean isSuccess = updater.compareAndSet(person, "whisper", "godyan"); System.out.println("修改后的name为:" + person.getName());//修改后的name为:godyan //测试2 String result=updater.getAndAccumulate(person, "hyuper",new BinaryOperator<String>() { @Override public String apply(String t, String u) { return t+u; } }); System.out.println("修改前的name为:"+result+"-修改后的name为:"+person.getName());//修改前的name为:godyan-修改后的name为:godyanhyuper } static class Person{ volatile String name; volatile int age; Person(int age,String name){ this.age=age; this.name =name; } public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } } }
五、增强版(Striped64、LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator)
1、Striped64累加器
Striped64是在java8中添加用来支持累加器的并发组件,它可以在并发环境下使用来做某种计数,Striped64的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64维护了一个base Count和一个Cell数组,计数线程会首先试图更新base变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过Cell数组来分散计数,Striped64根据线程来计算哈希,然后将不同的线程分散到不同的Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面,基于这种设计,最后的总计数需要结合base以及散落在Cell数组中的计数内容。这种设计思路类似于java7的ConcurrentHashMap实现,也就是所谓的分段锁算法,ConcurrentHashMap会将记录根据key的hashCode来分散到不同的segment上,线程想要操作某个记录只需要锁住这个记录对应着的segment就可以了,而其他segment并不会被锁住,其他线程任然可以去操作其他的segment,这样就显著提高了并发度,虽然如此,java8中的ConcurrentHashMap实现已经抛弃了java7中分段锁的设计,而采用更为轻量级的CAS来协调并发,效率更佳。
2、LongAdder是AtomicLong的增强版,在高并发的场景下性能比AtomicLong高,通过消耗更多的内存空间来提高性能。
即将一个变量进一步拆分到一个base数组中,减少资源竞争
3、LongAccumulator是LongAdder的增强版。LongAdder只能针对数值的进行加减运算,而LongAccumulator提供了自定义的函数操作。
4、DoubleAdder和DoubleAccumulator用于操作double原始类型。与LongAdder的唯一区别就是,其内部会通过一些方法,将原始的double类型,转换为long类型,其余和LongAdder完全一样:
参考:
juc-locks之原子框架
Striped64(累加器)