建议再看这篇文章之前可以先去了解一下jvm内存模型~
这里现提出一个问题,多线程环境下,我们怎么去保证i++的操作是线程安全的?
通过这个问题,我们引入今天要说的一个原子操作类——AtomicInteger,他可以实现各种原子性的操作,说到原子操作,不知道大家有没有了解过CAS,这个也可以用来做原子操作,既然都可以,那各位可以想想他们之间有什么联系~
我们先来看一段代码
java">@SpringBootTest
public class JVMTest {
public static void main(String[] args) {
int a= 3;
//AtomicInteger a=new AtomicInteger(3);
Person p=new Person();
p.id=12;
p.name="yinan";
getData(a,p);
System.out.println("主线程:"+a+" "+p.name);
}
public static void getData(int a, Person p){
// a.getAndIncrement();
a++;
p.name="yinan-change";
System.out.println(a+" "+p.name);
}
}
class Person{
int id;
String name;
}
在我们使用int作为a的基本类型的时候,以及我创建的对象Person,我们调用方法getData修改了对应的值之后,最后的在main方法中输出结果是什么呢?给我可以先思考一下,我这里就直接给运行截图了:
你会发现在最后main方法中打印出来的a依然是原来的值,这是因为java内存模型中基本数据类型是直接放在栈当中的,栈是线程内所有的,所以按照栈的特性,先进后出最后再方法里面输出的是4,当然方法结束后栈自然就被销毁了,由于栈是线程内的,所以自然不会同步到main方法当中的a;
那我们再来看看对象,对象的实例是放在堆当中的,而堆里面存放的是线程间共享的数据,相当于对象在创建的时候会在线程里面创建一个对象引用,然后指向堆中的那个对象,如果我的线程里面的这个对象数据被修改过后,那么就会通知到堆中的对象并进行修改
那我们该怎么去处理这种基本数据类型在一个线程中修改了,在其他线程中也能同步进行修改呢?
这里我们可以引出几种方法供各位参考:
- 采用数据的方法,将a转换为数组的方式,因为数组也是存放在堆当中的
- 使用AtomicInteger来实现
- 创建具有a类型的属性的对象
- 使用Optional
这里我们就讲一下AtomicInteger:
我们将a的类型改为AtomicInteger后,你会发现最后再main方法里面a的值也同时进行修改,看懂了怎么用之后我们再来看一下底层实现:
AtomicInteger底层采用Unsafe和volatile来保证线程安全和可见性,volatile这个关键字在前面有讲过,不理解怎么用的可以去看看:volatile关键字-CSDN博客
我们知道java方法是不能直接调用底层操作系统的,所以需要使用本地方法来进行操作,而Unsafe就相当于是c++中的指针,可以直接操作内存,所以这样就做到了操作内存。
当我们调用getAndIncrement方法的时候
我们再进入getAndAddInt方法
我们发现这个方法在Unsafe类下面,这个方法主要实现就是判断var1和var5的值是否相等,如果相等那就把var5+var4,var4就是++1操作,然后返回,如果不相等那就循环比较
这里再说一下CAS,这个东西主要是用来做乐观锁和锁自旋的,底层实现就是通过Unsafe来实现的,所以使用AtomicInteger是线程安全的,可以保证原子性操作,再加上对value使用了valotile关键字,保证了可见性,因此我在方法中对数据进行操作后可以同步到主内存当中共其他线程调用。
整个步骤大概流程是这样,中间可能有一些没有讲清楚的地方,可以在评论区说出你的疑问,我们共同学习。
最后,提出一个问题,CAS既然是用来保证线程安全的,那为什么不能使用synchronized来实现呢?