目录
1、可见性
如何产生的可见性问题?
java多线程在工作时,现将主内存中的数据读到线程工作内存(缓存),然后在工作内存中,对数据进行操作,操作完成后,再将数据写回到主内存。
- 产生一个可见性问题?
2线程中看不到1线程中操作过的数据,操作系统可能会对指令的执行先后顺序进行重新排序执行。导致内存中a=1,而不是我们期望的a=2;
概述
对于如今的多核处理器,每颗 CPU 都有自己的缓存,而缓存仅仅对它所在的处理器可见,CPU 缓存与内存的数据不容易保证一致。
为了避免处理器停顿下来等待向内存写入数据而产生的延迟,处理器使用写缓冲区来临时保存向内存写入的数据。写缓冲区合并对同一内存地址的多次写,并以批处理的方式刷新,也就是说写缓冲区不会即时将数据刷新到主内存中。缓存不能及时刷新导致了可见性问题。
2、有序性
- 有序性指的是程序按照代码的先后顺序执行。
- 编译器为了优化性能,有时候会改变程序中语句的先后顺序。
- Java 内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
3、原子性
线程切换带来的原子性(不可拆分)问题
一个或多个操作在 CPU 执行的过程中不被中断的特性,我们称为原子性.
eg:
i++(i=i+1)操作,分为先计算,后赋值,++操作不是原子操作,cpu在执行时可能会分成两步来执行。
- CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符。线程
切换导致了原子性问题
Java 并发程序都是基于多线程的,自然也会涉及到任务切换,任务切换的时机大多数是在时间片结束的时候。我们现在基本都使用高级语言编程,高级语言里一条语句往往需要多条 CPU 指令完成。如 count++,至少需要三条 CPU指令。
指令 1:首先,需要把变量 count 从内存加载到工作内存;
指令 2:之后,在工作内存执行 +1 操作;
指令 3:最后,将结果写入内存;
还是以上面的 count++ 为例。两个线程 A 和 B 同时执行 count++,即便 count 使用 volatile 修辞,我们预期的结果值是 2,但实际可能是 1。
4、总结(并发编程核心问题)
- 线程本地缓存,会导致可见性问题
- 线程切换执行,会导致原子性问题
- 编译优化重排指令,会带来有序性问题
5、volatile关键字
- 被volatile修饰后的共享变量,在一个线程中操作后,可以保证在另一个线程中立即可见的。
- 禁止进行指令重排序。
- volatile 不能保证对变量操作的原子性。
eg:
package com.ffyc.database.volatiledemo;
public class ThreadDemo implements Runnable{
/*
volatile 修饰的变量,在一个线程中被修改后,对其它线程立即**可见**、禁止cpu对指令重排序
*/
private vo