高并发下常见问题
并发中变量可见性问题
造成线程安全, 变量可见性问题的原因
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 典型的应用场景
- 只有一个修改者, 多个使用者, 要求保证可见性的场景
- 转态标识, 如示例中的介绍标识。
- 数据定期发布,多个获取者。