并发编程之原子操作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 {
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();//读写屏障 }
}
以上就是关于原子操作和Unsafe的解读,欢迎留言评论,谢谢。
并发编程之原子操作Atomic&Unsafe的更多相关文章
- C++11并发编程:原子操作atomic
一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一 ...
- 并发编程之原子Atomic&Unsafe
1.原子更新基本类型类 用于通过原子的方式更新基本类型,Atomic包提供了以下三个类: AtomicBoolean:原子更新布尔类型. AtomicInteger:原子更新整型. AtomicL ...
- c++并发编程之原子操作的实现原理
原子(atomic)本意是”不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作”. 处理器如何实现原子操作 (1) 使用总线锁保证原子性 如 ...
- 并发编程-JUC之Atomic
概述: 早期的JDK版本中,如果要并发的对Integer.Long.Double之类的Java原始类型或引用类型进行操作,一般都需要通过锁来控制并发,以防止数据不一致.JUC-Atomic原子类位于j ...
- java并发编程之原子操作
先来看一段简单的代码,稍微有点并发知识的都可以知道打印出结果必然是一个小于20000的值 package com.example.test.cas; import java.io.IOExceptio ...
- [转]JAVA并发编程学习笔记之Unsafe类
1.通过Unsafe类可以分配内存,可以释放内存:类中提供的3个本地方法allocateMemory.reallocateMemory.freeMemory分别用于分配内存,扩充内存和释放内存,与C语 ...
- Java并发编程之原子操作类
什么是原子操作类当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况.这时的一般策略是使用synchronized解决,因为synchronized能够保证多个线程不会同时更新该变量.然 ...
- 从CAS讲起,真正高性能解决并发编程的原子操作
今天是猿灯塔“365天原创计划”第1天. 一.原子性操作 原子性操作:原子性在一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉.及时在多个线程一起执行的时候,一个操作一旦 ...
- 并发编程(一)—— volatile关键字和 atomic包
本文将讲解volatile关键字和 atomic包,为什么放到一起讲呢,主要是因为这两个可以解决并发编程中的原子性.可见性.有序性,让我们一起来看看吧. Java内存模型 JMM(java内存模型) ...
随机推荐
- Java微服务(二):服务消费者与提供者搭建
本文接着上一篇写的<Java微服务(一):dubbo-admin控制台的使用>,上篇文章介绍了docker,zookeeper环境的安装,并参考dubbo官网演示了dubbo-admin控 ...
- 一起来读Netty In Action之传输(三)
当我们的应用程序需要接受比预期多很多的并发连接的时候,我们需要从阻塞传输切换到非阻塞传输上去,如果是我们的网络编程是基于jdk提供的API进行开发地的话,这种传输模式的切换几乎要我们重构整个网络传输相 ...
- bzoj2141_排队
题意 给定\(n\)个数,每次交换两个数,输出交换后的逆序数. 分析 交换两个数只会影响到对应区间内的逆序数,具体为减少区间\([l+1,r-1]\)中比\(a[r]\)大的数的个数,增加比\(a[r ...
- Day005作业
1,有如下变量(tu是个元祖),请实现要求的功能 tu = ("alex", [11, 22, {"k1": 'v1', "k2": [&q ...
- mariadb数据库galera下添加新的服务器节点
昨天经过各种努力,终于完成了两台服务器集成的搭建,今天再新开一台服务器,在想如何加入呢?网上度娘了很久结果没搜到相关文章:哎,索性直接照着前两台服务配置,在第三台(新服务器)上配置完成后重启maria ...
- Python连载37-多进程、进程子类、进程父子id
一.线程替代方案 1.subprocess (1)完全跳过线程,使用进程 (2)是派生进程的主要替代方案 (3)python2.4后引入 2.multiprocessing (1)使用threadin ...
- 如何在Centos服务器上搭建起Oracle10、VNC、以及FTP
一.重装和分区 1.配置所需磁盘阵列(Raid): 2.正确分区: 3.Centos安装:过于简单,请自行bd. 二.连网 系统安装完成之后,我们需为其分配IP和DNS: "编辑连接&quo ...
- 微软发布.Net Core 3.0 RC1,最终版本定于9月23日
2019.9.17 微软 宣布推出.NET Core 3.0 Release Candidate 1.就像Preview 9一样,主要专注于为 .NET Core 3.0 发布最终版本 .现在变得非常 ...
- ImageView的功能和使用
ImageView继承自View类,它的功能用于显示图片, 或者显示Drawable对象 xml属性: src和background区别 参考:http://hi.baidu.com/sunboy_2 ...
- html的表格边框为什么会这么粗?
因为默认情况下,cellspacing = 2px. 当表格的 border 不为 0 的时候,单元格(cell)的 border 为 1. 只有当表格的 border 设置为 0 的时候,单元格的 ...