看完后,你再也不用怕面试问并发编程啦( 六 )

  • 可见性:一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的 。
  • 原子性:相关操作不会中途被其他线程干扰,一般通过同步机制实现 。
  • 有序性:保证线程内串行语义 , 避免指令重排 。
9.1 可见性可见性表示的是 , 如果有线程更新了某一个共享变量的值 , 则其它线程要能够立即感知到最新的内容 。如果不能保证可见性,则可能出现类似于数据库中的脏读情况 。
前文介绍JMM的时候也提到了,如果要保证可见性,那么变量被一个线程修改后,需要将其修改后的最新值同步回主存,然后其它线程要读取该变量时,需要从主存刷新最新的值到本地内存 , 就这样通过主存实现可见性 。但是将最新值同步回主存的时机是没有强制要求的,也不知道其它线程什么时候可能会去从主存刷新最新值,所以普通变量在多线程操作时是保证不了可见性的 。
这时有一个比较好使的关键字:volatile 。JMM对它定义了一些特殊的访问规则,它能保证修改后的最新值能立即同步到主存,同时,每次使用都从主存刷新 。所以volatile能够保证多线程场景下的可见性 。
volatile 介绍
在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性” 。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值 。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度 。
volatile 使用package cn.itcast.thread;/** volatile 实现多线程访问共享成员时的可见性 */public class Test6 { // volatile 实现多线程访问共享成员时的可见性. private static volatile boolean flag = false; public static void main(String[] args) throws InterruptedException { // 创建线程1 new Thread(() -> { long num = 0; while (!flag){ num++; } // 如果没有打印 , 说明当前线程无法感知flag的修改 System.out.println("num = " + num); }).start(); // 休眠1000毫秒 Thread.sleep(1000); // 创建线程2 new Thread(() -> { // 修改flag flag = true; System.out.println("flag = " + flag); }).start(); }} 9.2 原子性在计算机中,它表示的是一个操作,可能包含一个或多个步骤,这些步骤要么全部执行成功要么全部执行失败,并且执行的过程中不能被其它操作打断,这类似于数据库中事务的原子性概念 。
数据原子操作,Java 内存模型对主内存与工作内存之间的具体交互协议定义了八种原子操作:
1. lock(锁定): 作用于主内存的变量,把一个变量标记为一条线程独占状态 。
2. unlock(解锁): 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 。
3. read(读取): 作用于主内存的变量 , 把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用 。
4. load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中 。
5. use(使用): 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎 。
6. assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量 。
7. store(存储): 作用于工作内存的变量 , 把工作内存中的一个变量的值传送到主内存中 , 以便随后的write的操作 。
8. write(写入): 作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中 。
如果要把一个变量从主内存中复制到工作内存中,就需要按顺序地执行read和load操作 , 如果把变量从工作内存中同步到主内存中,就需要按顺序地执行store和write操作 。但Java内存模型只要求上述操作必须按顺序执行 , 而没有保证必须是连续执行 。
对应如下的流程图:
看完后,你再也不用怕面试问并发编程啦

文章插图
比如:i = i + 1,就是一个非原子操作,它涉及到获取i , 获取1,相加,赋值等4个操作,所以在多线程情况下可能会出现并发问题 。
比如 i = i+1,我们要保证它的原子性 该怎么做呢?可以通过八种操作中的lock和unlock来达到目的 。但是JVM并没有把lock和unlock操作直接开放给用户使用,我们的Java代码中,就是大家所熟知的synchronized关键字保证原子性 。


推荐阅读