并发编程Volatile关键字

🌈并发编程 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又保证了可见性,所以就可以保证线程安全了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值