- Java内存模型
- 并发编程的三个重要特性
1. 原子性: ⼀个的操作或者多次操作,要么所有的操作全部都得到执⾏并且不会收到任何因素的⼲扰⽽中断,要么所有的操作都执⾏,要么都不执⾏。保证指令不会收到上下文切换的影响。synchronized修饰代码块。
2. 可见性:当⼀个变量对共享变量进⾏了修改,那么另外的线程都是⽴即可以看到修改后的最新值。保证指令不会受到cpu缓存的影响。volatile可以保证 共享变量的可见性。synchronized也能保证代码块中的可见性,但是属于重量级操作
3. 有序性: 代码在执⾏的过程中的先后顺序,保证指令不会受 cpu 指令并行优化的影响。volatile可以防止指令重排序 - 可见性
问题分析:A线程对t变量的修改值保存在自己的本地内存中,没有更新到主内存,导致B线程对t的读取不是最新值。
如何解决问题:1. Synchronized能保证可见性,不过他是通过monitor来实现的,属于重量级操作
2. volatile能保证可见性,可以用volatile修饰变量,被volatile修饰的变量在修改时会直接将内容写入到主存中,避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,适用于一写多读的情况。 - 有序性:JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,这就是指令重排。在单线程下不会有什么问题,但是在多线程环境下,就会有影响。如A1->B1->B2->A2->A3->B3,这个顺序对每个线程来说也是顺序的,但是可能会导致两个线程之间取到的某些变量值发生变化。
指令重排的优点:实现指令级并行,提高效率
!!!如何解决指令重排序问题?怎么解决?:volatile修饰变量能防止指令重排序。
Volatile通过内存屏障来防止指令重排序,对volatile变量的写指令会加入写屏障,对volatile变量的读指令会加入读屏障,写屏障保证在该屏障之前,对共享变量的改动都同步到主存当中,确保指令重排序时不会将写屏障之前的代码排在写屏障之后,读屏障保证在读屏障之后,对共享变量的读取,加载的是主存中最新数据,确保指令重排序时不会将读屏障之后的代码排在读屏障之前。
volatile关键字
volatile用来修饰成员变量和静态变量。
作用:1.保证可见性。2、防止指令重排序。
- volatile如何保证可见性:volatile修饰的变量发生改变后会立刻修改到主存中去。从汇编指令的角度看,volatile修饰的变量会多出一个lock前缀。将当前处理器的缓存行的数据写回到系统内存,同时使其他CPU里缓存了该内存地址的数据置为无效。
- volatile如何防止指令重排序:volatile通过读屏障和写屏障来防止指令重排,对volatile变量的写指令会加入写屏障,对volatile变量的读指令会加入读屏障,写屏障保证在该屏障之前,对共享变量的改动都同步到主存当中,确保指令重排序时不会将写屏障之前的代码排在写屏障之后,读屏障保证在读屏障之后,对共享变量的读取,加载的是主存中最新数据,确保指令重排序时不会将读屏障之后的代码排在读屏障之前。
- volatile不能解决的i++问题:i++可以拆分成三步,并非原子操作。1.读取i的值。2.进行i+1。3.更新i的值。线程A读出 i 的值为0,加一操作之前被阻塞,假设线程B已经将i的值从0变为2了,A读出的0值实际上已经是错误的了。viloate只能保证每次都重新读,修改后每次都立即刷新到主内存,但是这个操作的间隙是线程不安全的。换一句话说,volatile只能保证getstatic指令把i读取到操作栈顶时,i的值是正确的。
- 怎么解决i++问题:在i++上加锁,用原子类修饰i++。。。
- 还有哪些关键字能保证可见性:
- synchronized。同步代码块的可见性是由
对一个变量执行unlock操作之前,必须先把此变量同步回主内存中
。 - final。final关键字的可见性指:
被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this"的引用传递出去,那么其他线程就能看见final字段的值
,可以理解为一旦赋值就无法改变,其他线程读取都是一样的值。
- synchronized。同步代码块的可见性是由
volatile和synchronized的异同点。
- volatile关键字是线程同步的轻量级实现,所以volatile性能肯定⽐synchronized关键字要好。但是volatile关键字只能⽤于变量⽽synchronized关键字可以修饰⽅法以及代码块。
- 多线程访问volatile关键字不会发⽣阻塞,⽽synchronized关键字可能会发⽣阻塞
- volatile关键字能保证数据的可⻅性,但不能保证数据的原⼦性。synchronized关键字两者都能保证。
- volatile关键字主要⽤于解决变量在多个线程之间的可⻅性,⽽ synchronized关键字解决的是多个线程之间访问资源的同步性。