课程名称: 细说java多线程之内存可见性
课程地址:https://www.imooc.com/learn/352
课程评价:内容讲的一般吧,讲的知识体系不够完善,还有些讲错了。不怎么适合新手,老手也就图个检验所学知识。
第2章 2-1 可见性介绍
第3章 synchronized实现可见性
sychronized实现可见性的过程
这个讲师讲解大致的流程没错,但是概念上有多个地方是不对的。工作内存中值的清除并没有进行清除操作,而是cpu只是简单标注为失效,并且这个失效标注的动作,是在解锁的时候发生的,加锁的时候如果发现这个值失效,会从主内存重新读一次,所以讲师要注意,不要误导广大网友。
三种重排序
as-if-serial
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。
java内存中的变量都有指针引用,上下文引用成链,这个链是不会被打乱重排序的,只有没有数据依赖关系的代码,才会被冲排序,所以在单线程内部重排序不会改变程序运行结果,这就是as-if-serial
多线程发生可见性问题
大大的疑惑?
2.1和2.2间没有数据依赖关系?(好像没有)所以因为没有数据依赖关系,所以真的会存在这种重排序吗?
这样cpu不得多执行计算出mid变量,有时还用不上这个mid变量。
可见性问题分析
volatile
volatile的特性
volatile实现可见性的原理
-
这里提到插入一条sotre的屏障指令和一条load屏障指令,但是其实不够完善,volatile变量的读写前面也可能会加屏障指令。
void readAndWrite();
descriptor: ()V
flags:
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #4 // Field a:I
5: iconst_1 #iconst_1指令将number的值5压入栈后,cpu资源释放,此时即使number的值在工作内存失效,也不会导致 iadd重新从主内存获取6的值再进行 + 1
6: iadd
7: putfield #4 // Field a:I
10: return
LineNumberTable:
line 20: 0
line 21: 10
}
SourceFile: "Test.java"
解决原子性问题方案
从内存可见性角度讲,volatile读相当于加锁(volatile读时,前面的操作加了内存屏障,且对该volatile共享变量的写操作,它对volatile读是可见的,所以这些操作都跟synchronized加锁一样),volatile写相当于解锁(volatile写时,也会加入内存屏障,防止后面的操作跟volatile进行重排序等,对volatile写时,会强制刷新到主内存中,这跟synchronized解锁一样,解锁前需要把共享变量刷新到主内存中去,确保了可见性)
课程总结
- java语言层面有三种:synchronized,volatile,final
这是因为jvm的规范并没有要求64位的long或者double的写入是原子性的。在32位的机器上,一个64位变量的写入可能被拆分成两个32位的写操作来执行,这样如果只写入32位,而cpu释放,那么读取的线程就会只读到“”一半的值”。
volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。转自慕课问答,楼下有疑惑的亲们看一下吧~~~
package mkw.demo.syn;
public class SynchronizedDemo {
//共享变量
private boolean ready = false;
private int result = 0;
private int number = 1;
//写操作
public void write(){
ready = true; //1.1
number = 2; //1.2
}
//读操作
public void read(){
if(ready){ //2.1
result = number*3; //2.2
}
System.out.println("result的值为:" + result);
}
//内部线程类
private class ReadWriteThread extends Thread {
//根据构造方法中传入的flag参数,确定线程执行读操作还是写操作
private boolean flag;
public ReadWriteThread(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
//构造方法中传入true,执行写操作
write();
}else{
//构造方法中传入false,执行读操作
read();
}
}
}
public static void main(String[] args) {
SynchronizedDemo synDemo = new SynchronizedDemo();
//启动线程执行写操作
synDemo .new ReadWriteThread(true).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//启动线程执行读操作
synDemo.new ReadWriteThread(false).start();
}
}
package mkw.demo.vol;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class VolatileDemo {
private Lock lock = new ReentrantLock();
private int number = 0;
public int getNumber(){
return this.number;
}
public void increase(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lock.lock();
try {
this.number++;
} finally {
lock.unlock();
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final VolatileDemo volDemo = new VolatileDemo();
for(int i = 0 ; i < 500 ; i++){
new Thread(new Runnable() {
@Override
public void run() {
volDemo.increase();
}
}).start();
}
//如果还有子线程在运行,主线程就让出CPU资源,
//直到所有的子线程都运行完了,主线程再继续往下执行
while(Thread.activeCount() > 1){
Thread.yield();
}
System.out.println("number : " + volDemo.getNumber());
}
}