记录
第一章
减少上下文切换
- 无锁的并发编程
- CAS算法
- 减少线程
- 使用协程序。
死锁
进程拿到了锁但是,因为异常情况造成锁没有释放
- 避免一个线程同时获得多个锁
- 避免一个线程在锁内占用多个资源,保证每个锁只占用一个资源。
- 使用定时锁来代替内部锁的机制。
- 对于数据库锁,加锁和解锁都必须在一个数据库里面,否则会出现解锁失败。
资源限制
硬件上或者软件上的资源限制
使用线程不一定快,要考虑上下文切换和资源调度的时间,这些都是需要额外的开销,故有些时候串行甚至会比并行要快
- 硬件资源限制:使用集群并行执行程序(ODPS,Hadoop)
- 软件限制:资源池将资源复用
第二章
Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
volatile
volatile是轻量级的synchronized,不会引起线程上下文的切换和调度比synchronized的使用和执行成本更低,保证了“可见性”:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值
- 如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
volatile实现原则
- Lock前缀指令会引起处理器缓存回写到内存
- 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
synchronized
重量级锁,Java中的每一个对象都可以作为锁,synchronized用的锁是存在Java对象头里的。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Synchonized**括号里配置的对象**。
锁的升级与对比
竞争情况逐渐升级。锁可以升级但不能降级
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
优缺点对比
原子操作
不可被中断的一个或一系列操作
实现原子操作
- 使用总线锁保证原子性
所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。 - 使用缓存锁保证原子性
所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存
行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效
两种情况下处理器不会使用缓存锁定
- 当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line)时,则处理器会调用总线锁定。
- 有些处理器不支持缓存锁定。对于Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线定。
Java实现原子操作
- 使用循环CAS实现原子操作
- ABA问题
- 循环时间长开销大
- 只能保证一个共享变量的原子操作
- 锁
除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。
第三章
线程之间如何通信
- 共享内存
公共状态,隐式通信 - 消息传递
没有公共状态,显式通信
线程之间如何同步
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。同步是隐式进行的,Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。
抽象结构
所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享,局部变量,方法定义参数和异常处理器参数不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
如果线程A与线程B之间要通信的话,必须要经历下面2个步骤。
- 线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 线程B到主内存中去读取线程A之前已更新过的共享变量。
JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。
这里处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内存中读取另一个共享变量(A2,B2),最后才把自己写缓存区中保存的脏数据刷新到内存中(A3,B3)。当以这种时序执行时,程序就可以得到x=y=0的结果。从内存操作实际发生的顺序来看,直到处理器A执行A3来刷新自己的写缓存区,写操作A1才算真正执行了。虽然处理器A执行内存操作的顺序为:A1→A2,但内存操作实际发生的顺序却是A2→A1。此时,处理器A的内存操作顺序被重排序了,这里的关键是,由于写缓冲区仅对自己的处理器可见,它会导致处理器执行内存操作的顺序可能会与内存实际的操作执行顺序不一致。由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对写-读操作进行重排序。
happens-before
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile变量规则:对一个volatile域的写,happens-before于任意后续对这volatile域的读。
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。