并发中变量可见性问题
造成线程安全,变量可见性问题的原因
volatile 关键字的用途,使用场景
什么是并发中的变量可见性问题
-
问题 1.变量分为哪几类?
- 全局变量
- 局部变量
- 属性(静态的,非静态的)
- 本地变量
- 参数
-
问题 2,如何在多个线程间共享数据?
- 用全局变量:静态变量,或共享对象
-
问题 3,一个变量在线程 1 中被改变值了,在线程 2 中能看到该变量的最新值吗?
- ?
什么是并发中的变量可见性问题
public class VisibilityDemo {
//状态标识
//private static volatile boolean flag = true;
private static boolean flag = true;
public static void main(String [] args){
new Thread(new Runnable() {
@Override
public void run() {
int i= 0;
while (VisibilityDemo.flag){
// synchronized (this){
// i++;
// }
// i++;
}
System.out.println(i);
}
}).start();
try{
TimeUnit.SECONDS.sleep(2);
}catch(InterruptedException e){
e.printStackTrace();
}
VisibilityDemo.flag= false;
System.out.println("设置为false了");
}
}
-
并发的线程能不能看到变量的最新值,这就是并发中的变量可见性问题
- 为什么会不可见?
- 怎么才能可见?
怎么才能可见?
-
方式一: synchronized
-
方式二: volatile
为什么可见了呢?
JAVA 内存模型
JAVA 内存模型及操作规范
- 共享变量必须存放在主内存中
- 线程有自己的工作内存,线程只可操作自己的工作内存;
- 线程要操作共享变量,需从主内存中读取到工作内存,改变值后需从工作内存同步到主内存中。
JAVA 内存模型会带来什么问题?
请思考,有变量 A,多线程并发对其累加会有什么问题?如三个线程并发操作变量 A,大家读取 A 时都读到 A=0,都对 A+1,再讲值同步回主内存。结果是多少?
如何解决线程安全问题?
- 内存模型也产生了变量可见性问题。
如何让线程 2 使用 A 是看到最新值?
- 线程 1 修改 A 后必须立马同步回主内存;
- 线程 2 使用 A 前需要重新从主内存读取到工作内存。
疑问 1:使用前不会重新从主内存读取到工作内存吗?
疑问 2:修改后不会立马同步回主内存吗?
JAVA 内存模型同步交互协议,规定了 8 种原子操作:
- lock(锁定):将主内存中的变量锁定,为一个线程所独占
- unclock(解锁):将 lock 加的锁定解除,此时其它的线程可以有机会访问此变量
- read(读取):作用于主内存变量,将主内存中的变量值读取到工作内存当中
- load(载入):作用于工作内存变量,将 read 读取的值保持到工作内存中的变量副本中
- use(使用):作用于工作内存变量,将值传递给线程的代码执行引擎
- assign(赋值):作用于工作内存变量,将执行性引擎处理返回的值重新复制给变量副本
- store(存储):作用于工作内存变量,将变量副本的值传送到主内存中
- write(写入):作用于主内存变量,将 store 传送过来的值写入到主内存的共享变量中
JAVA 内存模型-同步交互协议,操作规范
如果要把一个变量从主内存复制到工作内存,那就要按顺序执行 read 和 load 操作,如果要把变量从工作内存同步回主内存,就要按顺序执行 store 和 write 操作。注意,Java 内存模型只要求上述两个操作必须按顺序执行,而没有保证是连续执行。也就是说 read 与 load 之间,sotre 与 write 之间是可插入其他指令,如对主内存中的变量 a,b 进行访问时,一种可能出现顺序是 read a,read b,load b,load a。除此之外,Java 内存模型还规定了在执行上述八种基本操作时必须满足如下规则:
- 不允许 read 和 load,store 和 write 操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
- 不允许一个线程丢弃它的最近的 assin 操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
synchronized 怎么做到可见性
-
Synchronized 语义规范:
- 进入同步块前,先清空工作内存中的共享变量,从主内存中重新加载。
- 解锁前必须把修改的共享变量同步回主内存
-
Synchronized 是如何做到线程安全的?
- 锁机制保护共享资源,只有获得锁的线程才可操作共享资源;
- Synchronized 语义规范保证了修改共享资源后,会同步回主内存,就做到了线程安全
volatile 的使用场景
-
volatile 的使用范围
- volatile 只可修饰成员变量(静态的,非静态的)。Why?
- 多线程并发下,才需要使用它。
-
volatile 典型的应用场景
- 只有一个修改者,多个使用者,要求保证可见性的场景
- 转态标识,如示例中的介绍标识。
- 数据定期发布,多个获取者。
volatile 与 synchronized 区别
-
volatile 本质是告诉 JVM 当前变量在寄存器中的值是不确定的,需要从主存中读取。synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。
-
volatile 仅能使用在变量级别,synchronized 则可以使用在变量、方法。
-
volatile 仅能实现变量修改的可见性,而 synchronized 则可以保证变量修改的可见性和原子性。《Java 编程思想》上说,定义 long 或 double 时,如果使用 volatile 关键字(简单的赋值与返回操作),就会获得原子性。(常规状态下,这两个变量由于其长度,其操作不是原子的)
-
volatile 不会造成线程阻塞,synchronized 会造成线程阻塞。
-
使用 volatile 而不是 synchronized 的唯一安全情况是类中只有一个可变的域。
-
当一个域的值依赖于它之前的值时,volatile 就无法工作了,如 n=n+1,n++ 等。如果某个域的值受到其他域的值的限制,那么 volatile 也无法工作,如 Range 类的 lower 和 upper 边界,必须遵循 lower<=upper 的限制。
-
使用 volatile 而不是 synchronized 的唯一安全的情况是类中只有一个可变的域。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于