版权声明:https://blog.csdn.net/qq_33249725 https://blog.csdn.net/qq_33249725/article/details/89716576
java如何解决可见性和有序性问题?
- 我们通过文章(https://blog.csdn.net/qq_33249725/article/details/89482779)
- 可知:Cpu缓存导致可见性问题,编译器优化导致有序性问题,我们能直接联想到的解决方法就是禁用Cpu缓存和编译器优化,但是这样程序的执行效率就会很低,我们可以自定义的去禁用吗?接下来就就是我们的Java内存模型。
java内存模型
- 按照需求去禁用Cpu缓存和编译器优化,java内存模型规范了JVM如何按照需禁用缓存和编译器优化的方法,这些方法包括volatile,synchronized,final三个关键字,以及Happens-Before规则。
volatile关键字
- volatile不是java的特产,早在C语言的时候就有了这个关键字,被volatile定义的变量其他线程可以立马知晓(因为被volatile修饰的变量在做写的时候直接操作内存,其他线程在读取的时候也是读取最新的值)
- volatile 和 synchronized 的区别volatile保证了可见性但是不能保证原子性,性能比synchronized高因为不发生线程切换,synchronized不仅保证了原子性而且还保证了可见性,具体如何取使用看具体的应用场景,后续会专门对volatile出一篇学习笔记。
- 使用volatile关键字的时候也会出现困扰,在JDK1.5之前的时候,具体的可以看一下如下代码示例:
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
// 这里 x 会是多少呢?
}
}
}
示例讲解:当线程A执行writer()操作的时候,线程B执行reader()方法这个时候X会是多少呢?在JDK1.5之前X的值可能是0也可能是42,那为什么会出现0的时候呢,因为可见性问题,在线程A执行writer()方法对X进行写操作在Cpu缓存中进行还没刷新到内存中线程B就执行了读的操作可能读取到的是内存中未被刷新的0。
在JDK1.5和JDK1.5之后X的值只会是42,因为JDK1.5之后对volatile进行了加强,添加了Happens-Before规范。
Happens-Before
-
程序的顺序规则 按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。
- 这一条比较好理解,前面对变量的操作对后续的可见,和单线程的执行逻辑相似。
-
传递性 字面意思很好理解就是可见性的传递。
- 假如A Happens-Before B,B Happens-Before C,那么A Happens-Before c 即A的操作对C可见。
上述代码的示例:
X=42 Happens-Before V=true
V =true Happens-Before V==true(这个应该很好理解,写在前 读在后)
按照传递性的特性: X=42 Happens-Before V=true,即X=42对V=true可见,所以在JDK1.5的时候上述代码X的只可能是42.
- 假如A Happens-Before B,B Happens-Before C,那么A Happens-Before c 即A的操作对C可见。
-
volatile 变量规则
- volatile修饰的变量写操作对后续这个变量的读操作可见,这个应该很好理解
-
锁的规则
- 这个规则是指对个锁的解锁动作对后续这个锁加锁可见。
- 举个例子:线程A获取锁将共享变量x(初始为0)值置为10,操作完成后释放锁,线程B获取锁,立马可以看到X等于10.
- 这个规则是指对个锁的解锁动作对后续这个锁加锁可见。
-
线程start()规则
- 指主线程A启动子线程B的时候,B线程可以看到主线程A启动子线程B的操作。
- 主线A在启动子线程将共享变量x(初始为0)值为10,在主线程A启动子线程B的时候,线程B可以看到共享变量x的值为10。
- 指主线程A启动子线程B的时候,B线程可以看到主线程A启动子线程B的操作。
-
线程的join()规则
- 指主线程A等待子线程B完成后主线线程能够看到子线程的操作。
- 举个示例: 子线程对共享变量x进行操作将x(初始为0)的值设置为10,主线程调用子线程B的join方法后,可以看到x的值为10。
- 指主线程A等待子线程B完成后主线线程能够看到子线程的操作。
-
线程中断规则
- 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
-
对象终结规则
- 个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
final关键字
- final关键字修饰的变量是不可变的,换句话也可以保住可见性。
总结
- 本文的学习笔记记录了java如何处理可见性和原子性问题,要想真正的学习java并发编程,了解其原理是第一步,重点还是要去实践,我们在写业务的同时的关注点除了业务本身的逻辑以外可以考虑一下数据的安全性问题。