volatile
-
特性:
- 保证可见性,不保证原子性
- 当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
- 这个写会操作会导致其他线程中的volatile变量缓存无效。
- 保证顺序性:禁止指令重排
- a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
- b.在进行指令优化时,不能将对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行
- 保证可见性,不保证原子性
-
单例模式需添加volatile来防止重排成a-c-b,造成其他线程误认为当前对象已创建好
a. memory = allocate() //分配内存 b. ctorInstanc(memory) //初始化对象 c. instance = memory //设置instance指向刚分配的地址
为什么volatile不能保证原子性
Java中只有对基本类型变量的赋值和读取是原子操作
- i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。
所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了
举例:
1、线程读取i
2、temp = i + 1
3、i = temp 当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6
4、然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1
以即使主存中的i值改变了,缓存一致性原则将B中的 i 变为新值,但是这个 i 值的改变也不会影响它将之前执行完的 i+1 得到的值赋给 i这一步,同样导致了最后的结果出错
CAS
-
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
-
在思想上属于乐观锁
-
cas的实现是利用unsafe提供的原子性操作方法。unsafe是jvm提供的方法,我们可以利用unsafe来实现硬件级别的原子操作
-
ABA
-
如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了
-
解决办法:变量前面加上版本号
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
-
-
循环时间长开销大
- pause指令(如果JVM能支持)
- 它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。
- 它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率
- pause指令(如果JVM能支持)
-
只能保证一个共享变量的原子操作
从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作
synchronized
- 使用方式
- 在需要同步的方法签名中加入synchorized关键字
- 对需要同步的代码块进行同步
- 对象锁:锁住一个对象
- synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁
- 类锁:锁住该类的Class对象
- 修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁
如何判断对象加锁
- 在markword里面存值,通过markword来判断对象是否已经加锁
- markword里面存的是线程锁状态状态还有hashcode