2.1volatile的应用
volatile是轻量级的sychronized,它可以保证共享变量的可见性。
可见性是指一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
不会引起线程上下文切换和调度。
2.1.1volatile的定义与实现原理
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应爱确保通过排他锁单读获取这个变量。Java语言提供volatile,在某些比锁情况下,使用比锁更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量值是一致的。
下面是一些CPU术语
volatile变量在底层实现可见性的原理:
在多处理器下,为了保证哥哥处理器的缓存是一致的,就会完全缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址别修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,就会重新重系统内存中把数据读到处理器缓存里。
两条实现规则:
1.Lock前缀指令会引起处理器缓存回写到内存。
Lock前缀指令锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,这个操作被称为“缓存锁定”
2.一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
2.1.2volatile的使用优化
根据处理器性质,添加字节到64字节。
2.2synchronized的实现原理与应用
synchronized实现的同步基础:Java中的每一个对象都可以作为锁。表现形式称为3种形式:
1.普通同步方法,锁时当前的实例对象
2.对于静态方法,琐是到你当前类的Class对象
3.对于同步方法块,琐是Synchonized括号里配置的对象。
如果一个线程访问同步代码块,首先必须获得锁,退出或者抛出异常时必须释放异常锁。
在JVM的实现原理:JVM时基于进入和退出Monitor对象来事项方法同步和代码块同步,但两者的实现细节不一致。代码块同步是指使用monitorenter和monitorexit指令实现的,而方法同步是使用另一种方法实现的。
2.2.1Java对象头
synchronized使用的锁时存在java对象头里,如果对象时数组,那么需要3个字宽,否则,2个字宽。
上图是32虚拟机的Mark word状态变化
2.2.2锁的升级与对比
锁的状态从低到高:无锁、偏向锁、轻量级锁和重量级锁。
锁可以升级,但是不能降级。
1.偏向锁
在对象头和栈帧中记录里存储锁偏向的线程ID,线程进入同步块不需要进行CAS操作来加锁和解锁只需要检测对象头里面是否指向当前线程的偏向锁。
(1)偏向锁的撤销
使用一种等到竞争出现才会释放锁的机制,所以当前线程尝试锁竞争偏向锁时,持有偏向锁的线程才会释放锁。
(2)关闭偏向锁
Java6和java7是默认启动
2.轻量级锁
(1)轻量级锁加锁
在当前线程的栈帧中存储锁空间,线程尝试使用CAS将对象头中的Mark Word替换位指向锁记录的指针,如果成功,当前线程获得锁,如果失败,其他线程竞争锁,当前线程尝试使用自旋来获取锁。
(2)轻量级锁解锁
使用CAS操作将Mark Word替换回到对象头,如果成功,则表示没有竞争发生,如果失败,表示锁存在竞争,锁就会膨胀成重量级锁。
3.锁的优缺点对比
2.3原子操作的实现原理
原子操作的意思是不可被中断的一个或者一系列操作。
2.3.1术语定义
2.3.2处理器如何实现原子操作
1.使用总线锁保证原子性
如果多个处理器同时对共享变量进程读写改操作(i++就是经典的读写改操作),那么共享变量就会被多个处理器同时修改,就不是原子操作。
2.使用缓存锁保证原子性
两种情况不能使用缓存锁定
1.操作数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。
2.处理器不支持缓存锁定
2.3.3Java如何实现原子操作
使用锁和循环CAS来实现原子操作
1.使用循环CAS实现原子操作
CAS实现基本思路是循环进行CAS、操作直到成功为止。
2.CAS实现原子操作的三大问题
1.ABA问题
使用CAS操作值,检测是否发生变化,如果没有发生,那么更新。如果一个值原来是A,变成B,然后变成A,实际是发生便变化了,解决这个是问题的思路是追加版本号。
2.循环时间开销大
自旋CAS长时间不成功,那么将会占用CPU资源
3.只能保证一个共享变量的原子操作
CAS只能保证一个共享变量的原子操作,如果多个共享变量可以合并成一个共享变量。
3.使用锁机制实现原子操作
锁机制保证只有获得锁的线程才能够操作锁定的内存区域。
JVM实现锁的方式是循环自旋CAS,一个线程想进入同步块,那么使用循环CAS的方式来获取锁,当它退出时,使用循环CAS释放锁。