🌈并发编程 volatile
什么是volatile?
<span style="background-color:#333333"><span style="color:#b8bfc6"> volatile是一个类型修饰符(type specifier),被设计用来修饰被不同线程访问和修改的变量。在程序设计中,尤其是在C语言、C++、C#和Java语言中,使用volatile关键字声明的变量或对象通常拥有和优化和(或)多线程相关的特殊属性。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。</span></span>
一、Volatile实现多线程共享变量的 -可变性
1.1多线程共享变量的不可变性
在多线程并发执行下,多个线程共享的成员变量,会出现一个线程修改共享变量后,另一个线程不能直接看到该变量修改后的最新值。
变量可见性,代码演示
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#c88fd0">package</span> <span style="color:#8d8df0">volatiledemo</span>;
<span style="color:#da924a">/**</span>
<span style="color:#da924a">* Created with IntelliJ IDEA.</span>
<span style="color:#da924a">*</span>
<span style="color:#da924a">* @Author: qiaozhan</span>
<span style="color:#da924a">* @Date: 2023/04/25/下午 06:58</span>
<span style="color:#da924a">* @Description:多线程下共享变量的不可见性</span>
<span style="color:#da924a">*/</span>
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">class</span> <span style="color:#8d8df0">VisibilityDemo01</span> {
<span style="color:#da924a">//第一个线程:主线程</span>
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">static</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">main</span>(<span style="color:#1cc685">String</span>[] <span style="color:#b8bfc6">args</span>) {
<span style="color:#da924a">//开启一个子线程</span>
<span style="color:#b8bfc6">MyThread</span> <span style="color:#b8bfc6">t</span> <span style="color:#b8bfc6">=</span> <span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">MyThread</span>();
<span style="color:#b8bfc6">t</span>.<span style="color:#b8bfc6">start</span>();
<span style="color:#da924a">//主线程执行</span>
<span style="color:#c88fd0">while</span> (<span style="color:#84b6cb">true</span>) {
<span style="color:#c88fd0">if</span> (<span style="color:#b8bfc6">t</span>.<span style="color:#b8bfc6">isFlag</span>()) {<span style="color:#da924a">//返回了flag的值</span>
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"主线程执行--"</span>);
}
}
}
}
<span style="color:#da924a">//子线程</span>
<span style="color:#c88fd0">class</span> <span style="color:#8d8df0">MyThread</span> <span style="color:#c88fd0">extends</span> <span style="color:#b8bfc6">Thread</span> {
<span style="color:#da924a">//成员变量</span>
<span style="color:#c88fd0">private</span> <span style="color:#1cc685">boolean</span> <span style="color:#b8bfc6">flag</span> <span style="color:#b8bfc6">=</span> <span style="color:#84b6cb">false</span>;
<span style="color:#b7b3b3">@Override</span>
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">run</span>() {
<span style="color:#b8bfc6">flag</span> <span style="color:#b8bfc6">=</span> <span style="color:#84b6cb">true</span>;
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"子线程修改后的flag="</span> <span style="color:#b8bfc6">+</span> <span style="color:#b8bfc6">flag</span>);
}
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">boolean</span> <span style="color:#b8bfc6">isFlag</span>() {
<span style="color:#c88fd0">return</span> <span style="color:#b8bfc6">flag</span>;
}
}</span></span>
不可见性代码演示
不可见性的原因:
主线程中读取到了flag的最新值主要原因是子线程很快就把flag改成了true,当主线程读取时,读取的是最新值。然而实际开发中,子线程可能会执行其他代码,这里用sleep代替
让子线程休眠一秒
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#c88fd0">package</span> <span style="color:#8d8df0">volatiledemo</span>;
<span style="color:#da924a">/**</span>
<span style="color:#da924a">* Created with IntelliJ IDEA.</span>
<span style="color:#da924a">*</span>
<span style="color:#da924a">* @Author: qiaozhan</span>
<span style="color:#da924a">* @Date: 2023/04/25/下午 06:58</span>
<span style="color:#da924a">* @Description:多线程下共享变量的不可见性</span>
<span style="color:#da924a">*/</span>
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">class</span> <span style="color:#8d8df0">VisibilityDemo01</span> {
<span style="color:#da924a">//第一个线程:主线程</span>
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">static</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">main</span>(<span style="color:#1cc685">String</span>[] <span style="color:#b8bfc6">args</span>) {
<span style="color:#da924a">//开启一个子线程</span>
<span style="color:#b8bfc6">MyThread</span> <span style="color:#b8bfc6">t</span> <span style="color:#b8bfc6">=</span> <span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">MyThread</span>();
<span style="color:#b8bfc6">t</span>.<span style="color:#b8bfc6">start</span>();
<span style="color:#da924a">//主线程执行</span>
<span style="color:#c88fd0">while</span> (<span style="color:#84b6cb">true</span>) {
<span style="color:#c88fd0">if</span> (<span style="color:#b8bfc6">t</span>.<span style="color:#b8bfc6">isFlag</span>()) {<span style="color:#da924a">//返回了flag的值</span>
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"主线程执行--"</span>);
}
}
}
}
<span style="color:#da924a">//子线程</span>
<span style="color:#c88fd0">class</span> <span style="color:#8d8df0">MyThread</span> <span style="color:#c88fd0">extends</span> <span style="color:#b8bfc6">Thread</span>{
<span style="color:#da924a">//成员变量</span>
<span style="color:#c88fd0">private</span> <span style="color:#1cc685">boolean</span> <span style="color:#b8bfc6">flag</span> <span style="color:#b8bfc6">=</span> <span style="color:#84b6cb">false</span>;
<span style="color:#b7b3b3">@Override</span>
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">run</span>() {
<span style="color:#da924a">//实际开发中,子线程可能会执行其他代码,这里用sleep代替</span>
<span style="color:#c88fd0">try</span> {
<span style="color:#b8bfc6">Thread</span>.<span style="color:#b8bfc6">sleep</span>(<span style="color:#64ab8f">1000</span>);
} <span style="color:#c88fd0">catch</span> (<span style="color:#b8bfc6">InterruptedException</span> <span style="color:#b8bfc6">e</span>) {
<span style="color:#b8bfc6">e</span>.<span style="color:#b8bfc6">printStackTrace</span>();
}
<span style="color:#b8bfc6">flag</span> <span style="color:#b8bfc6">=</span> <span style="color:#84b6cb">true</span>;
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"子线程修改后的flag="</span><span style="color:#b8bfc6">+</span><span style="color:#b8bfc6">flag</span>);
}
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">boolean</span> <span style="color:#b8bfc6">isFlag</span>() {
<span style="color:#c88fd0">return</span> <span style="color:#b8bfc6">flag</span>;
}
}</span></span>
因为子线程进入休眠状态,主线程就会在子线程修改flag之前执行,读取到的flag是false,但是sleep之后,子线程会继续执行 flag = true;,此时flag的值为true,但是主线程却读不到flag的最新值,便出现了多线程下修改变量的不可见性。
二、为什么会出现多线程共享变量的不可变性??
2.1JMM内存模型
了解共享变量的不可见行之前,首先需要了解一下java的内存模型(和java并发编程有关)
JMM(Java Memory Model):java内存模型是java虚拟机规范的一种内存模型,java内存模型是标准化的,它屏蔽掉了底层不同的计算机之间的区别。
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
JMM有以下规定:
-
所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
-
每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变景的工作副本。
-
线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
-
不同线程之间也不能直接访问对方工作内存中的变量,线程间变是的值的传递需要通过主内存中转来完成
和从git仓库中取代码道理相似
ps:本地内存和主存的关系
flag值在内存情况
结论:不可见性问题的原因
所有共享变量存在于主内存中,每个线程有自己的本地工作内存,而且线程读写共享数据也是通过本地内存和主内存进行交换,所以才导致了不可见性
三、变量不可见性的解决方案
解决方案: 加锁和使用volatile关键字
1.加锁
<span style="background-color:#333333"><span style="color:#b8bfc6">
<span style="color:#da924a">//主线程执行</span>
<span style="color:#c88fd0">while</span> (<span style="color:#84b6cb">true</span>) {
<span style="color:#c88fd0">synchronized</span> (<span style="color:#b8bfc6">t</span>){
<span style="color:#c88fd0">if</span> (<span style="color:#b8bfc6">t</span>.<span style="color:#b8bfc6">isFlag</span>()) {<span style="color:#da924a">//返回了flag的值</span>
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"主线程执行--"</span>);
}
}</span></span>
会什么加锁会读取到新的值???
线程进入synchronized代码块前后,执行过程入如下 a.线程获得锁 b.清空工作内存 c.从主内存拷贝共享变量最新的值到工作内存成为副本 d.执行代码 e.将修改后的本的值刷新回主内存中 f.线程释放锁
volatile关键字修饰。
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#da924a">//子线程</span>
<span style="color:#c88fd0">class</span> <span style="color:#8d8df0">MyThread2</span> <span style="color:#c88fd0">extends</span> <span style="color:#b8bfc6">Thread</span>{
<span style="color:#da924a">//成员变量</span>
<span style="color:#c88fd0">private</span> <span style="color:#c88fd0">volatile</span> <span style="color:#1cc685">boolean</span> <span style="color:#b8bfc6">flag</span> <span style="color:#b8bfc6">=</span> <span style="color:#84b6cb">false</span>;</span></span>
volatile关键字可见性原理:
当某共享变量被修改后,volatile会失效其它线程工作内存中的该变量的副本。
1.子线程t从主内存读取到数据放入其对应的工作内存 2.将flag的值更改为true,但是这个时候flag的值还没有写会主内存 3.此时main方法main方法读取到了flag的值为false 4.当子线程t将flag的值写回去后,失效其他线程对此变量副本 5.再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中
可见性总结: volatile 和 synchronized 都可以实现变量在多线程并发下的可见性。
volatile实现原理:当某共享变量被修改后,volatile会失效其它线程工作内存中的该变量的副本。
synchronized :清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本
synchronized,volatile都会让线程去重新获取主存中变量的值。
四、volatile的其他特性
volatile可以实现并发下共亨变量的可见性,除了volatile可以保证可见性外volatile还具备如下一些突出的特性:
1 volatile的原子性问题: volatlle不能保证原子性操作
2 禁止指令重排序: volatile可以防止指令重排序操作
4.1 volatlle不能保证原子性
准备100线程,每个线程执行10000次count++操作。
count++运行结果(加volatile后运行结果也是这样)
原子性问题: count++操作包含3个步骤: 。从主内存中读取数据到工作内存 。对工作内存中的数据进行++操作 将工作内存中的数据写回到主内存count++操作不是一个原子性操作,也就是说在某一个时刻对某一个操作的执行,有可能被其他的线程打断
count++操作
某一时刻,两个线程同时取到值100,并返回同样的值到主存中,也就是说两次count++操作,实际上count只加了一次。
1)假设此时X的值是100,线程A需要对改变量进行自增1的操作,首先它需要从主内存中读取变量x的值。由于CPU的切换关系,此时CPU的执行权被切换到了 B线程。A线程就处于就绪状态,B线程处于运行状态
2)线程B也需要从主内存中读取x变量的值,由于线程A没有对x值做任何修改因此此时B读取到的数据还是100
3)线程B工作内存中x执行了+1操作,但是未刷新到主内存中
4)此时CPU的执行权切换到了A线程上,由于此时线程B没有将工作内存中的数据刷新到主内存,因此A线程工作内存中的变量值还是100,没有失效 A线程对工作内存中的数据进行了+1操作
5)线程B将101写入到主内存
6)线程A将101写入到主内存 虽然计算了2次,但是只对A进行了1次修改。
如何保证原子性??
1.加锁 -- synchronized
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#c88fd0">package</span> <span style="color:#8d8df0">volatiledemo</span>;
<span style="color:#da924a">/**</span>
<span style="color:#da924a">* Created with IntelliJ IDEA.</span>
<span style="color:#da924a">*</span>
<span style="color:#da924a">* @Author: qiaozhan</span>
<span style="color:#da924a">* @Date: 2023/04/25/上午 08:53</span>
<span style="color:#da924a">* @Description:研究volatile的原子性问题,</span>
<span style="color:#da924a">* 定义一个共享变量</span>
<span style="color:#da924a">* 开启100个线程,每个线程累加变量10000次</span>
<span style="color:#da924a">*/</span>
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">class</span> <span style="color:#8d8df0">VolatileDemo02</span> {
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">static</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">main</span>(<span style="color:#1cc685">String</span>[] <span style="color:#b8bfc6">args</span>) {
<span style="color:#da924a">//创建线程任务对象</span>
<span style="color:#b8bfc6">Runnable</span> <span style="color:#b8bfc6">threadTarget</span> <span style="color:#b8bfc6">=</span> <span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">ThreadTarget1</span>();
<span style="color:#da924a">//开启100个线程对象</span>
<span style="color:#c88fd0">for</span> (<span style="color:#1cc685">int</span> <span style="color:#b8bfc6">i</span> <span style="color:#b8bfc6">=</span> <span style="color:#64ab8f">1</span>; <span style="color:#b8bfc6">i</span> <span style="color:#b8bfc6"><=</span> <span style="color:#64ab8f">100</span>; <span style="color:#b8bfc6">i</span><span style="color:#b8bfc6">++</span>) {
<span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">Thread</span>(<span style="color:#b8bfc6">threadTarget</span>,<span style="color:#d26b6b">"第"</span><span style="color:#b8bfc6">+</span><span style="color:#b8bfc6">i</span><span style="color:#b8bfc6">+</span><span style="color:#d26b6b">"个线程"</span>).<span style="color:#b8bfc6">start</span>();
}
}
}
<span style="color:#da924a">//线程任务类</span>
<span style="color:#c88fd0">class</span> <span style="color:#8d8df0">ThreadTarget1</span> <span style="color:#c88fd0">implements</span> <span style="color:#b8bfc6">Runnable</span>{
<span style="color:#da924a">//定义一个共享变量</span>
<span style="color:#c88fd0">private</span> <span style="color:#c88fd0">volatile</span> <span style="color:#1cc685">int</span> <span style="color:#b8bfc6">count</span> <span style="color:#b8bfc6">=</span> <span style="color:#64ab8f">0</span>;
<span style="color:#b7b3b3">@Override</span>
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">run</span>() {
<span style="color:#c88fd0">synchronized</span> (<span style="color:#b8bfc6">Thread</span>.<span style="color:#c88fd0">class</span>){
<span style="color:#c88fd0">for</span> (<span style="color:#1cc685">int</span> <span style="color:#b8bfc6">i</span> <span style="color:#b8bfc6">=</span> <span style="color:#64ab8f">0</span>; <span style="color:#b8bfc6">i</span> <span style="color:#b8bfc6"><</span> <span style="color:#64ab8f">10000</span>; <span style="color:#b8bfc6">i</span><span style="color:#b8bfc6">++</span>) {
<span style="color:#b8bfc6">count</span><span style="color:#b8bfc6">++</span>;
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#b8bfc6">Thread</span>.<span style="color:#b8bfc6">currentThread</span>().<span style="color:#b8bfc6">getName</span>()<span style="color:#b8bfc6">+</span><span style="color:#d26b6b">"count-----"</span><span style="color:#b8bfc6">+</span><span style="color:#b8bfc6">count</span>);
}
}
}
}</span></span>
4.2禁止指令重排序
4.21.什么是 指令重排序???
概述
在不影响运行结果的情况下,执行越快越好。
什么是重排序: 为了提高性能,编译和处理器常常会对既定的代码执行顺序进行指令重排序.
原因:一个好的内存模型实际上会放松对处理器和编译器规则的束缚,也就是说软件技术和硬件技术都为同一个目标而进行奋斗:在不改变程序执行结果的前提下,尽可能提高执行效率,JMM对底层尽量减少约束,使其能够发挥自身优势。因此,在执行程序时,为了提高性能,编译器和处理器常常会对指令进行重排序。
一般重排序可以分为如下三种:
1.编译优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
2.指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的.
1.5.2重排序的好处
可以提高程序处理速度。
源代码 底层执行
重排序带来的问题
代码w
<span style="background-color:#333333"><span style="color:#b8bfc6"><span style="color:#c88fd0">package</span> <span style="color:#8d8df0">volatiledemo</span>;
<span style="color:#da924a">/**</span>
<span style="color:#da924a">* Created with IntelliJ IDEA.</span>
<span style="color:#da924a">*</span>
<span style="color:#da924a">* @Author: qiaozhan</span>
<span style="color:#da924a">* @Date: 2023/04/25/下午 09:46</span>
<span style="color:#da924a">* @Description: 指令重排序出现的异常情况</span>
<span style="color:#da924a">*</span>
<span style="color:#da924a">* 执行结果有两种情况:</span>
<span style="color:#da924a">* 1;假如A线程先执行完此时【x = 0,y = 1】</span>
<span style="color:#da924a">* 2;假如B线程先执行完此时【x = 1,y = 0】</span>
<span style="color:#da924a">* 3. 假如A线程执行a=1后,cpu被线程B得到,B线程执行b=1,此时得到【x = 1,y = 1】</span>
<span style="color:#da924a">* ???有没有可能出现x = 0,y = 0呢??</span>
<span style="color:#da924a">*/</span>
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">class</span> <span style="color:#8d8df0">OutofOrderException</span> {
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">static</span> <span style="color:#1cc685">int</span> <span style="color:#b8bfc6">a</span><span style="color:#b8bfc6">=</span><span style="color:#64ab8f">0</span>,<span style="color:#b8bfc6">b</span><span style="color:#b8bfc6">=</span><span style="color:#64ab8f">0</span>;
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">static</span> <span style="color:#1cc685">int</span> <span style="color:#b8bfc6">x</span><span style="color:#b8bfc6">=</span><span style="color:#64ab8f">0</span>,<span style="color:#b8bfc6">y</span><span style="color:#b8bfc6">=</span><span style="color:#64ab8f">0</span>;
<span style="color:#c88fd0">public</span> <span style="color:#c88fd0">static</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">main</span>(<span style="color:#1cc685">String</span>[] <span style="color:#b8bfc6">args</span>) <span style="color:#c88fd0">throws</span> <span style="color:#b8bfc6">InterruptedException</span> {
<span style="color:#da924a">//定义两个线程</span>
<span style="color:#da924a">//线程A</span>
<span style="color:#b8bfc6">Thread</span> <span style="color:#b8bfc6">t1</span><span style="color:#b8bfc6">=</span> <span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">Thread</span>(<span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">Runnable</span>() {
<span style="color:#b7b3b3">@Override</span>
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">run</span>() {
<span style="color:#b8bfc6">a</span> <span style="color:#b8bfc6">=</span> <span style="color:#64ab8f">1</span>;
<span style="color:#b8bfc6">x</span> <span style="color:#b8bfc6">=</span> <span style="color:#b8bfc6">b</span>;
}
});
<span style="color:#da924a">//线程B</span>
<span style="color:#b8bfc6">Thread</span> <span style="color:#b8bfc6">t2</span><span style="color:#b8bfc6">=</span> <span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">Thread</span>(<span style="color:#c88fd0">new</span> <span style="color:#b8bfc6">Runnable</span>() {
<span style="color:#b7b3b3">@Override</span>
<span style="color:#c88fd0">public</span> <span style="color:#1cc685">void</span> <span style="color:#b8bfc6">run</span>() {
<span style="color:#b8bfc6">b</span> <span style="color:#b8bfc6">=</span> <span style="color:#64ab8f">1</span>;
<span style="color:#b8bfc6">y</span> <span style="color:#b8bfc6">=</span> <span style="color:#b8bfc6">a</span>;
}
});
<span style="color:#b8bfc6">t1</span>.<span style="color:#b8bfc6">start</span>();
<span style="color:#b8bfc6">t2</span>.<span style="color:#b8bfc6">start</span>();
<span style="color:#da924a">//获取执行结果</span>
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"x = "</span><span style="color:#b8bfc6">+</span><span style="color:#b8bfc6">x</span>);
<span style="color:#b8bfc6">System</span>.<span style="color:#b8bfc6">out</span>.<span style="color:#b8bfc6">println</span>(<span style="color:#d26b6b">"y = "</span><span style="color:#b8bfc6">+</span><span style="color:#b8bfc6">y</span>);
}
}</span></span>
<span style="background-color:#333333"><span style="color:#b8bfc6">* 执行结果有两种情况:
* 1;假如A线程先执行完此时【x = 0,y = 1】
* 2;假如B线程先执行完此时【x = 1,y = 0】
* 3. 假如A线程执行a=1后,cpu被线程B得到,B线程执行b=1,此时得到【x = 1,y = 1】
* ???有没有可能出现x = 0,y = 0呢??</span></span>
<span style="background-color:#333333"><span style="color:#b8bfc6">
package volatiledemo;
public class OutofOrderException {
public static int a=0,b=0;
public static int x=0,y=0;
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
count++;
a = 0;
b = 0;
x = 0;
y = 0;
//定义两个线程
//线程A
Thread t1= new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
//线程B
Thread t2= new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
//获取执行结果
System.out.print("第"+count+"次"+"x = "+x);
System.out.println(" y = "+y);
if (x == 0 && y == 0) {
break;
}
}
}
}</span></span>
出现了 x = 0,y = 0的情况。
很可能出现重排序
<span style="background-color:#333333"><span style="color:#b8bfc6">a = 1;
x = b;
b = 1;
y = a;</span></span>
变为下面的情况。
<span style="background-color:#333333"><span style="color:#b8bfc6">x = b;
a = 1;
y = a;
b = 1;</span></span>
使用volatile修饰变量。未出现重排序问题。
<span style="background-color:#333333"><span style="color:#b8bfc6">package volatiledemo;
/**
* Created with IntelliJ IDEA.
*
* @Author: qiaozhan
* @Date: 2023/04/25/下午 09:46
* @Description: 指令重排序出现的异常情况
*
* 执行结果有两种情况:
* 1;假如A线程先执行完此时【x = 0,y = 1】
* 2;假如B线程先执行完此时【x = 1,y = 0】
* 3. 假如A线程执行a=1后,cpu被线程B得到,B线程执行b=1,此时得到【x = 1,y = 1】
* ???有没有可能出现x = 0,y = 0呢??
*/
public class OutofOrderVolatile {
public static volatile int a=0,b=0;
public static volatile int x=0,y=0;
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
count++;
a = 0;
b = 0;
x = 0;
y = 0;
//定义两个线程
//线程A
Thread t1= new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
//线程B
Thread t2= new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
//获取执行结果
System.out.print("第"+count+"次"+"x = "+x);
System.out.println(" y = "+y);
if (x == 0 && y == 0) {
break;
}
}
}
}</span></span>
六、面试题:volatile和synchronized的区别
volatile只修饰实例变量和类变量,而synchronied可以修饰方法和代码块。
volatile可以保证数据的可见性,但是不保证原子性(多线程读写操作,不保证线程安全)
而synchronized是一种排他(互斥)机制,可以保证数据可见性和原子性。 volatile用于禁止指令重排序:可以解决单例双重检查对象初始化代码执行乱序问题。
volatile可以看做轻量版的synchronized,volatile不保证原子性,但是如果对一个共享变量进行多个线程的赋值,而没有其他操作,那么就可以用volatile来代synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就可以保证线程安全了。