目录
第一章
并发编程的目的:让程序运行更快。
但并不是启动更多的线程就能让程序最大限度并发执行。
想通过多线程让程序运行更快需要面对很多问题,比如:上下文切换问题、死锁问题、软硬件资源的限制问题等。
1.1上下文切换
一个进程可以有多个线程,即使单核处理器也可以多线程,CPU通过分配CPU时间片来实现,时间片一般是几十毫秒。
上下文切换回影响多线程的执行速度。
1.1.1 多线程一定快吗
答案是:不一定
循环次数 | 串行执行耗时/ms | 并发执行耗时 | 并发比串行快多少 |
1亿 | 130 | 77 | 约1倍 |
1千万 | 18 | 9 | 约1倍 |
1百万 | 5 | 5 | 差不多 |
10万 | 4 | 3 | 差不多 |
1万 | 0 | 1 | 慢 |
可以发现,并发执行累加操作不超过百万次时,并发比串行还要慢,是因为线程创建和上下文切换的开销。
1.1.2测试上下文切换次数时长
- 使用Lmbench3可以测量上下文切换时长。
- 使用vmstat可以测量上下文切换的次数。
上下文切换1秒1000多次。
1.1.3如何减少上下文切换
方法有:
- 无锁并发编程
多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同线程处理不同段的数据。
- CAS算法
Java的Atomic包使用CAS算法更新数据,不需要加锁。
- 使用最少线程
避免创建不需要的线程,否则会造成大量线程处于等待状态。
- 使用携程
在单线程里实现多任务的调度,并在单线程中维持多任务的切换。
1.1.4减少上下文切换实战
通过减少线上大量WAITING的线程,来减少上下文切换次数。
- 第一步:
用jstack命令dump线程信息,查看pid为xxxx的进程中的线程都在做什么
- 第二步:
统计所有线程的状态,例如发现300多个线程处于WAITING状态
- 第三步:
打开dump文件查看处于WAITING的线程在做什么,例如发现都是JBOSS的工作线程,说明JBOSS线程池里线程收到的任务太少。
- 第四步:
减少JBOSS的工作线程数,找到其线程池配置信息,将maxThreads降到100。
- 第五步:
重启,查看信息。
1.2死锁
避免死锁的几个常见方法:
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
1.3资源限制的挑战
(1)什么是资源限制
硬件资源限制有:带宽的上传/下载速度、硬盘读写速度、CPU处理速度。
软件资源限制有:数据库的连接数和Socket连接数等。
(2)资源限制引发的问题
资源限制可能会导致并发执行的速度低于串行。
(3)如何解决资源限制的问题
- 对于硬件资源限制:可以考虑用集群并行执行程序。既然单机资源有限,那就让程序在多机上运行。比如使用ODPS,Hadoop或者自己搭建服务器集群,不同的机器处理不同的数据。可以通过“数据ID%机器数”,计算得到一个机器编号,对应编号的机器处理这笔数据。
- 对于软件资源限制:可以考虑使用资源池将资源复用。比如使用连接池将数据库和Socket连接复用,或者在调用对方webservice接口获取数据时,只建立一个连接。
(4)在资源限制情况下并发编程
方法是:根据不同资源限制调整程序的并发度
比如:
下载文件程序依赖两个资源:带宽和硬盘读写速度。
有数据库操作时,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库连接。
1.4本章小结
本章介绍了并发编程时可能遇到的几个挑战及解决方案,对于Java开发工程师而言,此书作者强烈推荐多使用JDK并发包提供的并发容器和工具类来解决并发问题,因为这些类经过了充分的测试和优化,均可解决本章提到的几个挑战。
参考
参考《Java并发编程的艺术》——方鹏飞 魏鹏 程晓明