第一章 Java 并发编程的挑战
说明并发编程的世界中可能遇到的哪些问题,以及如何解决。
并发的目的是让程序运行得更快,但并不是更多的线程就能让程序最大限度地并发执行。在多线程情景下,面临诸多挑战,如上下文切换、死锁、受限于软硬件资源等问题。
1.1 上下文切换
Cpu 通过为每个线程分配 cpu 时间片来实现多线程执行代码,因为时间片非常的短,所以cpu 需要通过不停地切换线程执行,让我们感觉多个线程是在同时执行的。
但是,在切换之前cpu会保存上一个任务的状态,以便下次切换回任务时,会加载任务的状态。
所以任务从保存到再加载的过程就是一次上下文切换。
很明显,当程序中的多个线程存在大量的上下文切换,程序执行的速度未必会比串行来得快。
1.1.1 减少上下文切换
- 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以通过将数据的ID 按照 Hash算法取模分段,不同线程处理不同段的数据的方式,避免使用锁。
- CAS 算法。Java 的 Atomic 包使用CAS 算法来更新数据,不需要加锁
- 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
- 协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
1.2 死锁
死锁,即多个线程因某些问题无法释放锁,导致多个线程之间互相等待的场景。
避免死锁的常见方法:
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
- 尝试使用定时锁,使用 lock.tryLock(timeout) 来替代使用内部锁机制
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
1.3 资源限制的挑战
资源限制是指程序受限于软硬件的条件,而不能达到预期的处理效果。主要包含有:带宽的上传/下载速度、硬盘读写速度、CPU处理速度。数据库连接数、socket连接数等。
引发的问题:并发执行时,因为增加了上下文切换和资源调度时间的原因。程序运行时可能会更慢。
解决方法:使用ODPS、Hadoop或者搭建服务器集群,不同机器处理不同的数据。可以通过 “数据ID%机器数”,计算出一个机器编号,然后由对应编号的机器处理这笔数据。
在资源受限的情况下进行并发编程:需要根据不同的资源限制调整程序的并发度。