并发编程之原子操作Atomic&Unsafe
原子操作:不能被分割(中断)的一个或一系列操作叫原子操作。
原子操作Atomic主要有12个类,4种类型的原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,原子更新引用。Atomic包中的类基本都是使用Unsafe实现的包装类。
基本类型:AtomicInteger,AtomicLong,AtomicBoolean;
引用类型:AtomicReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference;
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;
属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;
1、原子更新基本类型类
下面我们来看一下每种类型的一个实例:
/** * <p>Title: AtomicIntegerTest.java</p > * <p>Description: </p > * <p>Copyright: NTT DATA Synergy All Rights Reserved.</p > * <p>Company: www.synesoft.com.cn</p > * <p>@datetime 2019年8月9日 上午8:01:30</p >* <p>$Revision$</p > * <p>$Date$</p >* <p>$Id$</p >*/ package com.test; import java.util.concurrent.atomic.AtomicInteger; /** * @author hong_liping * */ public class AtomicIntegerTest { static AtomicInteger ai=new AtomicInteger(); public static void main(String[] args) { for(int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { ai.incrementAndGet(); } }).start(); } // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println("循环后的结果如下:"+ai.get()); } }
//测试结果
循环后的结果如下:9
循环后的结果如下:10
根据上面的代码,我们多运行几次,会发现,代码的测试结果一会儿是9一会儿是10,不是10,为什么呢,因为线程还没有跑完,我下面的就已经打出来了,让线程睡眠一下就可以解决这个问题了。
下面我们来看一下atomic的ABA问题,这个问题在面试的时候经常问到。
/** * <p>Title: AtomicTest.java</p > * <p>Description: </p > * <p>@datetime 2019年8月8日 下午3:40:37</p >* <p>$Revision$</p > * <p>$Date$</p >* <p>$Id$</p >*/ package com.test; import java.util.concurrent.atomic.AtomicInteger; /** * @author hong_liping * */ public class AtomicAbaTest { private static AtomicInteger ato=new AtomicInteger(1); public static void main(String[] args) { Thread mainT=new Thread(new Runnable() { @Override public void run() { int a=ato.get(); System.out.println(Thread.currentThread().getName()+"原子操作修改前数据"+a); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } boolean successFlag=ato.compareAndSet(a, 2); if(successFlag){ System.out.println(Thread.currentThread().getName()+"原子操作修改后数据"+ato.get()); } } },"mainT"); Thread otherT=new Thread(new Runnable() { @Override public void run() { int b=ato.incrementAndGet();//1+1 System.out.println(Thread.currentThread().getName()+"原子操作自增后数据"+b); b=ato.decrementAndGet();//2-1 System.out.println(Thread.currentThread().getName()+"原子操作自减后数据"+b); } },"OtherT"); mainT.start(); otherT.start(); } }
测试结果:
OtherT原子操作自增后数据2
mainT原子操作修改前数据1
OtherT原子操作自减后数据1
mainT原子操作修改后数据2
根据上面的操作,我们可以看到的是AtomicInteger的操作自增,自减,值的替换等。但是此处应当注意的是原子操作存在一个ABA问题,ABA问题的现象就是:mainT执行完成后的值2(替换的2),otherT在执行2-1的时候的2是自增(1+1)的结果。在这两个线程中用到的2不是同一个2,就相当于是一个漏洞,相当于说你从王健林账号中偷走了10个亿去投资,等你投资好了回本了,你再把这10个亿打回了王健林账号,这整个过程王建林没有发现,你的整个操作过程也没有记录,所以对于王健林来说他的钱没有丢失过,还是放在那里的。很明显要解决这个ABA问题最好的办法就是每一步操作都打个标记,相当于一个银行的流水,这样你偷钱,还钱的整个过程就有一个出,一个入,王健林看的时候就会发现我的总金没有变,但是操作记录显示我的钱曾经被人盗了然后又被人还回来了。这就需要用到AtomicStampeReference.
2、原子更新引用类型
接下来我们来看一下AtomicStampedReference的测试类:
/** * <p>Title: AtomicStampedReference.java</p > * <p>Description: </p > * <p>@datetime 2019年8月9日 上午8:35:56</p >* <p>$Revision$</p > * <p>$Date$</p >* <p>$Id$</p >*/ package com.test; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; /** * @author hong_liping * */ public class AtomicStampedReferenceTest { private static AtomicStampedReference<Integer> asf=new AtomicStampedReference<Integer>(1, 0); public static void main(String[] args) { Thread mainT=new Thread(new Runnable() { @Override public void run() { int stamp= asf.getStamp(); System.out.println(Thread.currentThread().getName()+"原子操作修改前数据"+asf.getReference()+ "_"+stamp); try