第1章 并发编程的挑战
并发编程的目的是为了让程序运行得更快,但是不是更多的线程就能让程序最大限度的并发执行。比如上下文切换、死锁的问题,以及受限于软件和硬件的资源限制问题。
软件资源限制:有数据库的链接数和socket连接数等
硬件的资源限制有带宽的上传、下载速度、硬盘读写速度和CPU处理速度。
减少上下文切换的方法
- 无锁并发编程
- CAS算法
- 使用最少线程
- 使用协程
避免死锁
- 避免一个线程获得多个锁
- 避免一个线程在锁内同时占有多个资源
- 尝试使用定时锁带替代内部锁机制
- 数据库锁,加锁和解锁在一个数据库连接里
建议使用并发容器和工具类来解决并发问题
第2章 并发机制的底层实现原理
java代码在编译后会变成java字节码,字节码被类加载器加载到jvm当中,jvm执行字节码,最终需要转化为汇编指令在CPU上执行。
在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的synchronized。
volatile
特性:
- 可见性
如果对声明了volatile的变量进行写操作,jvm就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会系统内存,同时根据处理器的缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的数据是否过期,过期就要设置无效重新加载。
- 禁止指令重排
synchronized
实现同步的基础,java中每一个对象都可以作为锁:
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是Synchonized括号里面的配置对象
每个对象有一个monitor与之关联,获得monitor获得锁,代码块同步是使用monitorenter和monitorexit实现的,插入到同步代码块的前后位置。检查是否持有monitor所有权,即尝试获得对象的锁
偏向锁
为了解决统一线程多次活动一个锁,需要切换上下文等代价。是一种等到竞争出现才释放锁的机制
做法:
- 在锁对象的对象头和栈帧的锁记录里存储锁偏向的线程ID
- 如果检测没有偏向锁,检测Mark Word中的锁标识是否设置为1,如果没有设置就是用CAS竞争锁
- 如果设置了1,就尝试用CAS将对象的偏向锁指向当前线程
轻量级锁
加锁:
- 在当前线程的栈帧中创建用于存储锁记录的空间,将对象头中的Mark Word复制到锁记录中,然后尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针,失败则与其他线程竞争,尝试自旋获得锁。
解锁:
- CAS将栈帧中存储的Mark Word替换回对象中,失败就代表存在竞争,膨胀为重量级锁
锁升级过程
这几种都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。