Java 内存模型

Java 内存模型

看Java 如何解决可见性和有序性问题

并发编程bug之源:

  1. 可见性
  2. 原子性
  3. 有序性

先看如何解决可见性和有序性问题–Java内存模型

什么是Java内存模型?

  1. 缓存导致可见性
  2. 编译优化导致有序性

那么解决可见性,有序性最直接的办法就是禁用缓存和禁用编译优化,但这样问题虽然解决了,但程序就有性能问题了。

合理的方案是按需禁用缓存以及编译优化。

那么,怎么做到“按需禁用”呢?所谓“按需禁用”,其实就是指按照程序员的要求来禁用。所以,为了解决可见性和有序性问题,只需要提供给程序员按需禁用缓存和编译优化的方法即可。

Java内存模型可以从不同的视角来解读,站在程序员的视角,本质上可以理解为,Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法。

具体来说包括以下方法:

  1. volatile
  2. synchronized
  3. final
  4. 六项 Happens-Before 规则

使用volatile的困惑

volatile关键字不是Java语言的特产,C语言中也有,volatile表示不稳定的,变化的。它最原始的意义就是禁用CPU缓存。

如:我们声明一个 volatile 变量:

volatile int x = 0 

它表达的是:告诉编译器,对这个变量的读写,不能使用CPU缓存,必须从内存中读取或写入。

Java内存模型在1.5版本对 volatile 语义进行了增加。怎么增加的呢?答案就是一项 Happens-Before 规则。

Happens-Before 规则

Happens-Before 并不是说前面一个操作发生在后续操作的前面,它真正要表达的是:前面一个操作的结果对后续操作是可见的。

Happends-Before 规则就是要保证线程之间的这种“心灵感应”,一个人心之所想,另一个人都看得到。

正式的说法是: Happends-Before 约束了编译器的优化行为,虽然允许编译器优化,但是要求编译器优化后一定要遵守 Happends-Before 规则。

1. 程序的顺序性规则

这条规则指一个线程中,按照程序顺序:程序前面对某个变量的修改一定是对后续操作可见的。
这是比较符合单线程里面的思维的。

2. volatile 变量规则

这条规则是指对一个 volatile 变量的写操作,Happens-Before 于后续对这个 volatile 变量的读操作。

3. 传递性

这条规则指:如果 A Happends-Before B,且 B Happends-Before C,那么 A Happends-Before C。

4. 管程中锁的规则

这条规则指:对一个锁的解锁 Happends-Before 于后续对这个锁的加锁。

要理解这个规则,首先要了解“管程指的是什么”。

管程是一种能用的同步原语,在Java 中指的就是 synchronized,synchronized 是Java 里对管程的实现。

管程中的锁在Java 里是隐式实现的,例如下面的代码,在进入同步块之前,会自动加锁,而在代码块执行完,会自动释放锁,加锁以及释放锁都是编译器帮我们实现的。

synchronizedthis{ // 此处自动加锁
	// x是共享变量,初始值 = 10
	ifthis.x < 12{
		this.x = 12;
	}
} // 此处自动解锁 

5.线程 start() 规则

这条是关于线程启动的。它是指主线程A启动子线程B后,子线程B能看到主线程在启动子线程B之前的操作。

Thread B = new Thread(()->{
  	// 主线程调用 B.start() 之前
  	// 所有对共享变量的修改,此处皆可见
  	// 此例中,var==77
});
// 此处对共享变量 var 修改
var = 77;
// 主线程启动子线程
B.start();

6. 线程 join() 规则

这条是关于线程等待的。它是指主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。

Thread B = new Thread(()->{
  // 此处对共享变量 var 修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程 B 可见
// 主线程启动子线程
B.start();
B.join() // 主线程等待子线程B执行完成
// 子线程所有对共享变量的修改
// 在主线程调用 B.join() 之后皆可见
// 此例中,var==66

被我们忽视的final

前面讲 volatile 为的是禁用缓存以及编译优化,我们再从另一方面来看,有没有办法告诉编译器优化的更好一点呢?就是使用final 关键字了。

final修饰变量时,初衷是告诉编译器:这个变量是永远不变的,可以做死的优化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值