一篇文章轻松搞懂Atomic
今天聊一下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;是可分割的,所以他不是一个原子操作。
3、Atomic
哎呀,终于到正题了,因为要说这个东西必须要理解前面的一些概念。进入今天的正题
AtomicInteger
废话不说,看下AtomicInteger的源码,可以看到上面的技术都在了,unsafe ,volatile,CAS
简单的说就是AtomicInteger 通过 volatile保证了线程之间的可见性,不存在读取的问题
通过cas 保证了赋值的问题,不存在修改的问题。 (还是存在一定问题)
AtomicReference
这个类有必要说一下,因为之前一直没有从底层理解这个类。
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用
从代码可以看到value 是volatile,当时我的疑问 就是虽然我不改变value引用,但是我改变里面的值保证不了同步啊。
不知道你是不是也有同样的疑问😅
先说结论: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问题吗? 你知道如何解决吗? 欢迎留言给我
- 点赞
- 收藏
- 关注作者
评论(0)