并发编程之原子Atomic&Unsafe
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 { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败 boolean successFlag=asf.compareAndSet(1, 2, stamp, stamp+1); if(successFlag){ System.out.println(Thread.currentThread().getName()+"原子操作修改后数据"+asf.getReference()+ "_"+stamp); }else{ System.out.println(Thread.currentThread().getName()+"cas操作失败"); } } },"mainT"); Thread otherT=new Thread(new Runnable() { @Override public void run() { int stamp=asf.getStamp(); asf.compareAndSet(1, 2, stamp, stamp+1); System.out.println(Thread.currentThread().getName()+"原子操作自增后数据"+asf.getReference()+ "_"+asf.getReference()); asf.compareAndSet(2, 1, stamp, stamp+1); System.out.println(Thread.currentThread().getName()+"原子操作自减后数据"+asf.getReference()+ "_"+stamp);; } },"OtherT"); mainT.start(); otherT.start(); } } //测试结果: mainT原子操作修改前数据2_0 OtherT原子操作自增后数据2_2 OtherT原子操作自减后数据2_0 mainTcas操作失败
接下来我们来看一下AtomicIntegerArray的一个案例
/** * <p>Title: AtomicArrayTest.java</p > * <p>Description: </p > * <p>@datetime 2019年8月10日 上午9:45:49</p > * <p>$Revision$</p > * <p>$Date$</p > * <p>$Id$</p > */ package com.test; import java.util.concurrent.atomic.AtomicIntegerArray; import com.sun.org.apache.bcel.internal.generic.NEWARRAY; /** * @author hong_liping * */ public class AtomicArrayTest { static int[] array=new int[]{1,2,3}; static AtomicIntegerArray aia=new AtomicIntegerArray(array); public static void main(String[] args) { aia.getAndSet(1, 5); System.out.println(aia.get(1)); System.out.println(array[1]); if(aia.get(1)==array[1]){ System.out.println("数组中的值与原子数组中的相等"); }else{ System.out.println("数组中的值与原子数组中的不相等"); } } }结果:
5
2
数组中的值与原子数组中的不相等
由以上的代码可以看出原子数组与我本身定义的数据同一个下标下的值是不一样的,为什么呢,我们看一下源码就会发现原子数据操作的并不是我定义的变量本身,而是先拷贝一份,然后操作的是拷贝的版本。
public AtomicIntegerArray(int[] array) { // Visibility guaranteed by final field guarantees this.array = array.clone();//初始化数组的时候拷贝 }
public final int getAndSet(int i, int newValue) { return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue); }
在进行数据原子操作的时候使用的是魔术类Unsafe.
4、原子更新字段类
接下来我们再来看看AtomicIngerFieldUpdater
/** * <p>Title: AtomicIntegerFieldUpdateTest.java</p > * <p>Description: </p > * <p>@datetime 2019年8月10日 上午10:02:22</p > * <p>$Revision$</p > * <p>$Date$</p > * <p>$Id$</p > */ package com.test; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * @author hong_liping * */ public class AtomicIntegerFieldUpdateTest { static AtomicIntegerFieldUpdater aifu=AtomicIntegerFieldUpdater.newUpdater(Person.class, "age"); static class Person{ private String name; public volatile int age; public Person(String name,int age){ this.name=name; this.age=age; } public int getAge(){ return age; } } public static void main(String[] args) { Person person=new Person("张三", 18); System.out.println(aifu.getAndIncrement(person)); System.out.println(aifu.get(person)); } } 测试结果:
18
19
在age属性上加volatile是为了保证在多线程并发的情况下保证可见性。
Unsafe
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。 Unsafe类为一单例实现,提供静态方法getUnsafe获取Unsafe实例,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常。
@CallerSensitive /* */ public static Unsafe getUnsafe() /* */ { /* 88 */ Class localClass = Reflection.getCallerClass(); /* 89 */ if (!VM.isSystemDomainLoader(localClass.getClassLoader()))// 仅在引导类加载器`BootstrapClassLoader加载时才合法 /* 90 */ throw new SecurityException("Unsafe"); /* 91 */ return theUnsafe; /* */ } /* */
Unsafe经常用到的就是CAS,内存屏障(禁止load,store重新排序),线程调度(线程挂起,恢复还有获取,释放锁)。
如何获取Unsafe,1、把调用Unsafe相关方法的类Demo所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载 java -Xbootclasspath/Demo:${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
2、通过反射获取单例对象theUnsafe
我们可以看一下下面的一个代码:
public class UnsafeInstance { public static Unsafe reflectGetUnsafe(){ Field field; try { field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } return null; } }
接下来再来看一个利用Unsafe的代码:
/** * <p>Title: AtomicUnsafeUpdaterTest.java</p > * <p>Description: </p > * <p>@datetime 2019年8月10日 上午10:57:23</p > * <p>$Revision$</p > * <p>$Date$</p > * <p>$Id$</p > */ package com.test; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import sun.misc.Unsafe; /** * @author hong_liping * */ public class AtomicUnsafeUpdaterTest { private String name; private volatile int age; private static final Unsafe unsafe=UnsafeInstance.reflectGetUnsafe(); private static final long valueOffset; static{ try { valueOffset=unsafe.objectFieldOffset(AtomicUnsafeUpdaterTest.class.getDeclaredField("age"));//偏移量 System.out.println("initial valueOffset is "+valueOffset); } catch (Exception e) { throw new Error(e); } } public void compareAndSwapAge(int old,int target){ unsafe.compareAndSwapInt(this, valueOffset, old, target); } public AtomicUnsafeUpdaterTest(String name,int age){ this.name=name; this.age=age; } public int getAge(){ return this.age; } public static void main(String[] args) { AtomicUnsafeUpdaterTest test=new AtomicUnsafeUpdaterTest("美女",30); test.compareAndSwapAge(30, 25); System.out.println("年龄变换后的值为"+test.getAge()); } }
1、CAS(unsafe的用法)的几个重要方法以及参数:
/** * CAS * @param o 包含要修改field的对象 * @param offset 对象中某field的偏移量 * @param expected 期望值 * @param update 更新值 * @return true | false */ public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
上述中的偏移量是什么呢,我们来看一下:AtomicUnsafeUpdaterTest的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicUnsafeUpdaterTest对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。
2、unsafe线程调度
包括线程挂起、恢复、锁机制等方法。
public class ThreadParkerTest { public static void main(String[] args) { /*Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("thread - is running----"); LockSupport.park();//阻塞当前线程 System.out.println("thread is over-----"); } }); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(t);//唤醒指定的线程*/ //拿出票据使用 LockSupport.park(); System.out.println("main thread is over"); //相当于先往池子里放了一张票据 LockSupport.unpark(Thread.currentThread());//Pthread_mutex System.out.println("im running step 1"); } }
public class ObjectMonitorTest { static Object object = new Object(); /* public void method1(){ unsafe.monitorEnter(object); } public void method2(){ unsafe.monitorExit(object); }*/ public static void main(String[] args) { /*synchronized (object){ }*/ Unsafe unsafe = UnsafeInstance.reflectGetUnsafe(); unsafe.monitorEnter(object);//获取锁 //业务逻辑写在此处之间 unsafe.monitorExit(object);//锁释放 }
3、内存屏障
public class FenceTest { public static void main(String[] args) { UnsafeInstance.reflectGetUnsafe().loadFence();//读屏障 UnsafeInstance.reflectGetUnsafe().storeFence();//写屏障 UnsafeInstance.reflectGetUnsafe().fullFence();//读写屏障 } }
并发编程之原子Atomic&Unsafe的更多相关文章
- 并发编程之原子操作Atomic&Unsafe
原子操作:不能被分割(中断)的一个或一系列操作叫原子操作. 原子操作Atomic主要有12个类,4种类型的原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,原子更新引用.Atomic包中的类 ...
- 并发编程-JUC之Atomic
概述: 早期的JDK版本中,如果要并发的对Integer.Long.Double之类的Java原始类型或引用类型进行操作,一般都需要通过锁来控制并发,以防止数据不一致.JUC-Atomic原子类位于j ...
- C++11并发编程:原子操作atomic
一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一 ...
- java并发编程学习: 原子变量(CAS)
先上一段代码: package test; public class Program { public static int i = 0; private static class Next exte ...
- Java多线程并发编程之原子变量与非阻塞同步机制
1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 -- 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞 ...
- Java并发编程之原子变量
原子变量最主要的一个特点就是所有的操作都是原子的,synchronized关键字也可以做到对变量的原子操作.只是synchronized的成本相对较高,需要获取锁对象,释放锁对象,如果不能获取到锁,还 ...
- [转]JAVA并发编程学习笔记之Unsafe类
1.通过Unsafe类可以分配内存,可以释放内存:类中提供的3个本地方法allocateMemory.reallocateMemory.freeMemory分别用于分配内存,扩充内存和释放内存,与C语 ...
- 并发编程 20—— AbstractQueuedSynchronizer 深入分析
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 并发编程 05—— Callable和Future
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
随机推荐
- Python——列表赋值的若干用例
原创声明:本文系博主原创文章,转载或引用请注明出处. 1. 直接赋值 >>> a = [1,2,3,4,5] >>> b = a >>> id(a ...
- css 命名规范 BEM
在项目的开发过程当中, 我们往往因为日益复杂的css代码而感到力不从心. 如何合理的组织css代码成为了我们前端开发过程中必须考虑到的环节. 在读element源代码的时候, 了解到了BEM的命名风格 ...
- 函数参数-arguments-reset参数
1.JS中用:arguments 1)存放实参的集合,是一个类似于数组的对象,只有数组的 length,没有数组方法 function add1(a,b,c) { console.log(argume ...
- Pandas中DataFrame数据合并、连接(concat、merge、join)之concat
一.concat:沿着一条轴,将多个对象堆叠到一起 concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False, key ...
- FastDFS为什么要结合Nginx?
为什么选择Nginx Nginx 是一个很牛的高性能Web和反向代理服务器, 它具有有很多非常优越的特性: 在高连接并发的情况下,Nginx是Apache服务器不错的替代品: Nginx在美国是做虚拟 ...
- 设计一个Mypoint类,求两个点之间的距离
package Test; public class test6 { public static void main(String[] args) { // TODO Auto-generated m ...
- Prism框架实战——订餐软件
参考B站刘铁猛老师的订餐软件https://www.bilibili.com/video/av29782724?from=search&seid=6787438911056306128 环境: ...
- 灰度图像--图像分割 Scharr算子
学习DIP第46天 转载请标明本文出处:http://blog.csdn.net/tonyshengtan ,出于尊重文章作者的劳动,转载请标明出处!文章代码已托管,欢迎共同开发: https://g ...
- windows驱动开发详解学习笔记
1. windows驱动分两类,NT式驱动和WDM驱动,后者支持即插即用: 2. DriverEntry是入口函数,传入参数:pDriverObject由IO管理器传入: 3. WDM驱动中,AddD ...
- 【WebStorm】前端工具开发利器webstrom专篇
---------------------------------------------------------------------------------[亲身实测] WebStorm混搭sv ...