java并发编程的艺术
一、并发挑战
- 上下文切换耗时,减少上下文切换
- 访问临界资源引入锁的机制,但容易造成死锁,锁的管理复杂
- 软硬件资源限制导致大量线程阻塞
二、底层机制(volatile synchronized)
volatile
-
保证了共享变量可见性(每个线程都能读取到变量的最新值)
-
volatile涉及到cpu指令,编译成汇编指令后会在指令前添加lock
- 处理器缓存行的数据写回到内存
- 写回内存会使缓存了该内存地址的数据无效
-
相关内存模型简单介绍
cpu不会和内存做直接数据交互而是通过缓存。在多核cpu中,如果一个内存中的数据被多个cpu缓存,且用了volitale关键字定义,变量修改后会写回到内存。这个过程中会锁住缓存,只有当前线程的缓存结果可以刷新内存,同时每个cpu都在嗅探总线上数据以保证缓存数据、内存数据之间的一致性,缓存了对应数据的cpu会使缓存失效,下一次读取需要从内存加载。 -
volatile优化
LinkedTransferQueue通过追加字节方式,实现队头和队尾不会保存到同一个缓存行,导致修改队头锁住了队尾,影响出队入队效率(和缓存行大小有关),但是会占用额外的缓存空间。
synchronized
1.6之前重量级锁(更符合操作系统教科书中锁的定义)
1.6只有引入了偏向锁和轻量级锁
Java中的锁通常是一个对象
- 普通同步方法,锁是实例对象
- 静态同步方法,锁是当前类对象
- 同步方法快,锁是括号里的对象
synchronized在jvm实现是监视器对象,通过monitorenter和monitorexit指令实现
锁相关信息存储在对象头中
- 一个字长:hashCode,锁信息,GC标记
- 一个字长:类型指针
- 一个字长:数组长度(仅限数组)
锁可以升级不能降级(为了提高获得锁和释放锁的效率)
- 偏向锁
CAS方式获取,可以剥夺(处于全局安全点),有竞争就会剥夺,竞争激烈会有频繁的获取与撤销浪费CPU资源。 - 轻量级锁
CAS获取,失败自旋,在释放时如果CAS失败,升级成重量级锁,原来的竞争线程阻塞。释放完毕唤醒阻塞线程。CAS自旋消耗CPU - 重量级锁
普遍认识中的锁,竞争成功,只想代码知道主动释放;竞争失败,阻塞线程,有释放锁线程唤醒。