并发编程常识
并发编程的目的
让程序运行的更快
并发编程本质
CPU通过给每个线程分配CPU时间片(几十毫秒)来实现;每个线程执行完一个时间片后就会切换到下一个时间片执行;但是切换前后需要保存上一个任务的状态(程序计数器)
多线程一定快吗
不一定,如果一个串行任务让分别让一个单线程和一个多线程执行;单线程执行的快。因为多线程有创建和上下文切换的开销。
如何减少上下文切换
使用无锁并发方案(如CAS)、单线程能解决的问题就不要多线程解决
并发编程中需要考虑的问题
资源限制
java 并发机制的底层实现原理
java -> class -> JVM执行字节码 -> 转为汇编指令 -> cpu上执行
本质上是 jvm实现(栈+计数器) + cpu的指令
Volatile
volatile与Synchronized区别
- 从并发角度:
- volatile不会导致线程阻塞,不会引起线程上下文切换和调度
- synchronized 会导致线程阻塞,重量级
- 编译角度
- volatile 标记的变量不会被编译器优化
- synchronized 编辑的变量可以被编译器优化
- 使用角度
- volatile只能修饰变量
- synchronized 即能修饰变量,又可以修饰方法
- 语义角度
- volatile 强调的是一致性
- synchronized 情调的不仅是一致性,还有原子性 (排他锁的方式独占这个变量或者方法)
CPU术语
术语 | 英文 | 术语描述 |
---|---|---|
内存屏障 | memory barriers | 是一组处理器指令,用于实现对内存操作的顺序限制 |
缓存行 | chache line | 缓存中可以分配的最小存储单位。处理器填写缓存线时会加载整个缓存线,<br />需要使用多个主内读取周期 |
原子操作 | atomic operations | 不可中断的一个或者一系列操作 |
缓存行填充 | cache line fill | 当处理器识别到从内存中读取操作数是可缓存的,<br />处理器读取真个缓存行到适当的缓冲(L1,L2,L3的或所有) |
缓存命中 | cache hit | 如果进行高速缓存行填充导致的内存位置仍然是下次处理器访问的地址时,<br />处理器从缓存中读取操作数,而不是从内存读取 |
写命中 | write hit | 当处理器将操作数写回到一个内存缓存区域时,它首先会检查这个缓存的<br />内存地址是否在缓存行中,如果存在一个有效的缓存行,则处理器将这个<br />操作数据写回到缓存,而不是写回到内存,这个操作被称为写命中 |
写缺失 | write misses the cache | 一个有效的缓存行被写入到不存在的内存区域 |
volatile原理
背景:cpu为了提高运行效率,cpu是不会直接和系统内存直接通信,cpu是与缓存行进行通信的,这样就需要操作的数据会放入到缓存行中,如果是多核处理器,那么不同的缓存中对应的数据一致性就需要保障;缓存行与内存数据的一致性就称为了问题
volatile 修饰的变量,对应的汇编指令 会有一个Lock前缀的指令
Lock前缀指令作用:
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的操作会使在其它CPU中缓存了该内存地址的数据置为无效

Synchronized原理
背景
jdk5之前,synchronized是一个重量级锁,但是jdk6之后,多其进行了优化
修饰范围 | 锁住对象 |
---|---|
普通方法 | 当前实例对象 |
静态方法 | 当前class对象 |
同步方法 | 括号里配置的对象 |
Synchronized是在jvm中实现的,依赖于monitor,主要是monitorenter和monitorexit两条指令实现;这两条指令均是在编译期插入到class程序中
mark word
synchronized用的锁存在java对象头中
对象在内存中存储的布局可以分为3块区域:
- 对象头(mark word + 类型指针,指向类元数据的指针)
- 对象实例
- 对齐填充
mark word组成:锁状态、hashcode、分代年龄、是否偏向锁、锁标志位
锁状态 | hashcode | 分代年龄 | 是否偏向锁 | 锁标志位 |
---|---|---|---|---|
无锁状态 | 01 | |||
轻量级锁 | 00 | |||
重量级锁 | 10 | |||
GC标记 | 11 | |||
偏向锁 | 1 | 01 |
锁状态:(锁能升级,但是不能降级)
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁

原子操作的实现原理
原子:不能进一步分割的最小粒子
原子操作:不可被中断的一个或一系列操作
处理器如何实现原子操作
- 通过总线锁保证原子性
- 通过缓存锁保证原子性
java中如何实现原子操作
- 循环CAS实现原子操作
- 锁
CAS带来的问题
- ABA问题
- 循环时间开销大
- 只能保证一个共享变量的原子操作
网友评论