第1章 并发编程的挑战
- 并发编程的目的是为了让程序运行的更快,但并不是启动更多的线程就能让程序最大限度地并发执行
- 运行中会遇到很多问题,如上下文切换、死锁以及受限于硬件和软件的资源限制等
1.1 上下文切换
- 即使是单核处理器也支持多线程执行代码,CPU是通过给每个线程分配CPU时间片来实现多线程的
- 时间片是CPU分配给各个线程的时间,时间片非常短,一般是几十毫秒
- CPU是通过不停的切换线程执行,因此感觉是多个线程在同时进行
- CPU通过时间片分配算法来循环执行任务
- 当任务执行一个时间片后会切换到下一个任务,在切换前会保存上一个任务的状态,以便在下次切换回这个任务时可以加载任务的状态
- 任务从保存到再加载的过程就是一次上下文切换
1.1.1 多线程一定快吗
- 当并发执行累加操作不超过百万次时,速度会比串行执行累加操作要慢
- 原因是并发执行中,线程有创建和上下文切换的开销
1.1.2 测试上下文切换次数和时长
- 使用Lmbench3可以测量上下文切换的时长
- 使用vmstat可以测量上下文切换的次数
1.1.3 如何减少上下文切换
- 无锁并发编程
- 多线程竞争锁时会引起上下文切换
- 将数据的ID按照Hash算法取模分段,不同线程处理不同段的数据
- 多线程竞争锁时会引起上下文切换
- CAS算法
- Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 使用最少线程
- 避免创建不需要的线程,防止出现大量等待线程
- 协程
- 在单线程里实现多任务调度,并在单线程里维持多个任务间的切换
1.2 死锁
- 避免死锁的常见方法
- 避免一个线程同时获得多个锁
- 避免一个线程在锁内同时占用多个资源
- 使用定时锁,使用lock.tryLock(timeout)来替代内部锁机制
- 对于数据库锁,加锁和解锁必须在同一个数据库连接里,否则会出现解锁失败的情况
1.3 资源限制
1.3.1 资源限制概述
- 服务器的带宽只有2Mb/s,资源下载速度时1Mb/s,系统启动10个线程下载资源,下载速度不会变成10Mb/s
- 硬件资源限制有带宽的上传/下载速度、硬盘读写速度和CPU的处理速度
- 软件资源限制有数据库的连接数和socket连接数等
1.3.2 资源限制引发的问题
- 并发编程中将代码执行速度加快的原则,即将代码中串行执行的部分变成并发执行
- 如果将某段串行的代码并发执行,因受限于资源仍然在串行执行
- 这时程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间
1.3.3 如何解决资源限制问题
- 对于硬件资源限制问题,可以使用集群并行执行程序
- 使用ODPS、Hadoop或者搭建服务器集群,不同的处理器处理不同的数据
- 对于软件资源限制问题,可以考虑使用资源池将资源复用
- 使用连接池将数据库和Socket连接复用
1.3.4 如何在资源限制的情况下进行并发编程
- 根据不同的资源限制调整程序的并发度