一篇文章轻松搞懂Atomic

香菜聊游戏 发表于 2022/06/26 21:24:21 2022/06/26
【摘要】 今天聊一下Java的Atomic 这一组的类,在开发多线程的时候必须要会的一组,也是很高效的一组,一定要学会的。1、 乐观锁和悲观锁在开始聊之前,先说下锁的两个定义,一种是乐观锁,一种是悲观锁。悲观锁就是考虑事情总是假设最坏的情况,假定每次去访问资源总有人竞争,只有获得对象锁才安心。乐观锁就是总是假设最好的情况,也就是访问资源的时候可能没人竞争,即使可能有人竞争,但是很快就结束了。从上面的描...

今天聊一下Java的Atomic 这一组的类,在开发多线程的时候必须要会的一组,也是很高效的一组,一定要学会的。

1、 乐观锁和悲观锁

在开始聊之前,先说下锁的两个定义,一种是乐观锁,一种是悲观锁。

悲观锁就是考虑事情总是假设最坏的情况,假定每次去访问资源总有人竞争,只有获得对象锁才安心。

乐观锁就是总是假设最好的情况,也就是访问资源的时候可能没人竞争,即使可能有人竞争,但是很快就结束了。

从上面的描述可以看出来,乐观锁的效率更高,因为乐观锁不会无脑的去加锁,不启动锁效率更高。

2、CAS

unsafe

Unsafe是JDK内部的工具类,主要实现了平台相关的操作,可以理解为直接操作内存的工具

sun.misc.Unsafe 是JDK内部用的工具类。它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。

一般情况下,这个类是不让使用的,但是我们可以通过反射进行调用。

CAS

CAS = compare and swap,翻译过来就是 比较并交换。

CAS 操作包含三个操作数—内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么会自动将该位置值更新为新值。

否则,不做任何操作。一个线程从主内存中得到num值,并对num进行操作,写入值的时候,线程会把第一次取到的num值和主内存中num值进行比较,如果相等,就会将改变后的num写入主内存,如果不相等,则一直死循环对比,会消耗CPU,直到成功为止。也叫自旋锁。

cas 底层是调用的机器指令,可以保证原子性。一定要理解这句话。

底层原理:

最终调用了一条汇编指令:lock cmpxchg 指令,来实现底层 cas 的。

也就是 cpu 中有一条 cmpxchg 指令。

但是这条指令不是原子的,也就是拿出来和比较是两个操作,中间有可能被别人打断。

所以需要在这个过程加上 lock,可以简单理解为把内存总线锁住,别人不允许修改这块内存。

volatile

volatile 是 Java 中的关键字,是一个变量修饰符,被用来修饰会被不同线程访问和修改的变量。

volatile 主要有两个作用

1、禁止指令重排

volatile 修饰的变量的读写指令不能和其前后的任何指令重排序,其前后的指令可能会被重排序。

2、线程可见性

在 Java 内存模型中,所有的变量都存储在主存中,同时每个线程都拥有自己的工作线程,用于提高访问速度。线程会从主存中拷贝变量值到自己的工作内存中,然后在自己的工作线程中操作变量,而不是直接操作主存中的变量,由于每个线程在自己的内存中都有一个变量的拷贝,就会造成变量值不一致的问题。

不加volatile的时候,每个线程都会有共享变量的副本,加上volatile之后,相当于取消了副本,每次访问的时候都会直接读取共享变量。

但是,这个时候有个问题,就是如果修改变量不是原子性的,会造成线程不安全。

比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。

image.png

3、Atomic

哎呀,终于到正题了,因为要说这个东西必须要理解前面的一些概念。进入今天的正题

AtomicInteger

废话不说,看下AtomicInteger的源码,可以看到上面的技术都在了,unsafe ,volatile,CAS

image.png

简单的说就是AtomicInteger 通过 volatile保证了线程之间的可见性,不存在读取的问题

通过cas 保证了赋值的问题,不存在修改的问题。 (还是存在一定问题)

AtomicReference

这个类有必要说一下,因为之前一直没有从底层理解这个类。

AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用

从代码可以看到value 是volatile,当时我的疑问 就是虽然我不改变value引用,但是我改变里面的值保证不了同步啊。

不知道你是不是也有同样的疑问😅

image.png 先说结论:AtomicReference原子性的作用是对”对象”进行原子操作,也就是可以对对象的多个属性保证原子修改同步。

再来说下是怎么实现的,也就是解释我的疑问.

我们看下下面的代码,调用的是compareAndSwapObject

// Unsafe.h 
virtual jboolean compareAndSwapObject(
::java::lang::Object *, 
jlong, ::java::lang::Object *, 
::java::lang::Object *);
// natUnsafe.cc 
static inline bool compareAndSwap (volatile jobject *addr, jobject old, jobject new_val) 
{ 
jboolean result = false; 
spinlock lock; // 如果字段的地址与期望的地址相等则将字段的地址更新 
if ((result = (*addr == old))) 
    *addr = new_val; 
    return result; 
}

jboolean sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset, 
jobject expect, jobject update) { 
// 获取字段地址并转换为字符串 
jobject *addr = (jobject*)((char *) obj + offset);
// 调用 compareAndSwap 方法进行比较 
return compareAndSwap (addr, expect, update);
}

解释,compareAndSwap 是对数据的偏移地址进行比较,也就是说你对对象修改了任意一个属性的话,整个对象的偏移量会改变,cas就会比较不通过。

4、总结

Atomic是一组比较简单的乐观锁的实现,在开发的过程中可以根据情形选用。

Atomic适用线程数较少的等待时间短的,或者说竞争比较少的情况。因为会有死循环

如果竞争比较多的建议还是用锁,这样可以让线程等待。

最后你知道cas 存在的ABA问题吗? 你知道如何解决吗? 欢迎留言给我


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。