1. 线程安全的实现方法
1.1 互斥同步(悲观锁)
synchronized
特性
- 重量级,Java线程映射到操作系统内核线程,所以会导致用户态到内核态的切换
- 同一条线程是可重入的
monitorenter
执行时吧对象的锁计数器加一,monitorexit
会把计数器减一,减为0时才真正释放锁
- 无法强制已获取锁的线程释放锁,也无法强制正在等待锁的线程中断或者退出
This Monitor 和 Class Monitor
根据修饰的方法类型来决定取所在的对象实例this还是取类型对应的Class对象作为线程持有的锁
- this monitor
//黄色Java高并发详解 P72
- class monitor
1.2 非阻塞同步(乐观锁)
CAS
指令
CAS需要三个操作数,目标内存地址、旧的预期值和准备设置的新值。
当且仅当目标内存地址中的值和旧的预期值相等时,处理器才会用准备设置的新值更新目标内存地址。这是一个原子操作。
虽然CAS简单高效,但是逻辑漏洞是:
如果一个变量V的值从A被其他线程改成了B之后又改回了A,CAS还是认为此值没有变化过,这个问题互斥同步会比CAS更有效解决。
1.3 无同步
1.3.1 可重入代码
纯代码,绝对不会出现出现因为线程调度出现错误的代码
1.3.2 线程本地存储
如果共享数据能保证在一个线程中执行,就可以把共享数据可见范围限制在同一个线程内
2.锁优化
2.1自旋锁与自适应自选
自旋
由于Java线程(轻量级进程)直接对应的是内核线程,所以挂起线程和恢复线程都会造成用户态和内核态的切换,如果锁被占用的时间很短,自选等待的就会省去系统状态切换所花费的资源。如果锁被占用时间长,自旋等待就会浪费CPU资源。
自适应改进:
如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过锁,那在以后要获取这个锁时将有可能直接省略掉自旋过程,以避免浪费处理器资源。
2.2 锁消除
锁消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。
经过逃逸分析之后,判断到一段代码中堆上的所有数据都不会逃逸出去被其他线程访问到,那就可
以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须再进行。
2.3 锁粗化
总是推荐将同步块的作用范围限制得尽量小,是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
//for循环中加锁的代码
2.4 轻量级锁和重量级锁
主要操作Java对象的对象头的Markword部分。包括哈希码、GC年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
可偏向部分在下节
即将进入同步块的时候,若此对象的标志位为01,即没有没锁定,就在当前线程的栈帧中创建一个空间命名为锁记录,并把MarkWord中的信息(除标志位之外的部分)都拷贝到此区域。
重量级锁用ObjectMonitor类表示。故此时指向重量级锁的指针是指向Java堆里的某个对象
2.5 偏向锁、轻量级锁和重量级锁
当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。
偏向锁的支持需要在对象并没有hashCode的情况下
2.5.1 升级全过程
关于偏向锁升级轻量级锁的过程中,在暂停原有线程的时候,会不会为对象填充hashCode
是把
线程的的ID+epoch
拷贝到栈中,还是分配完hashCode再拷贝到栈中,有待于研究!!!!!!!!
2.5.2 可偏向中的Epoch与批量重偏向
偏向锁对效率的影响
一个线程多次进入同步块时,偏向锁性能开销极低,但当其他线程尝试获得锁时,就需要等到全局安全点时将偏向锁撤销为无锁状态或升级为轻量级/重量级锁。这个操作使锁的效率大大降低,所以如果说运行时存在大量多线程竞争,偏向锁会导致性能下降。
解决方案:批量重偏向和批量偏向撤销
- 某个类的对象频繁发生偏向撤销的操作,当次数超过预定的阈值时,就批量撤销该类所有对象的偏向。这个优化操作,就叫做批量撤销偏向。
- 一个线程先分配了一定数量的同一类型对象作为锁,在该线程操作完成后,另一个线程接着执行同步操作,但是这两个线程并不存在变量逃逸,我们就可以将前一个线程拥有的偏向重偏向到后一个线程,这个优化操作,就叫做批量重偏向。
Epoch与优化操作的关系
到底如何操作的,网上的还是抄来抄去,我仍然怀疑他们的正确性,待我搭建好JVM源码调试环境之后,我要亲自看看这个epoch是怎么干活的,然后再更