Java并发编程的艺术(一)——并发编程需要注意的问题
并发是为了提升程序的执行速度,但并不是多线程一定比单线程高效,而且并发编程容易出错。若要实现正确且高效的并发,就要在开发过程中时刻注意以下三个问题:
- 上下文切换
- 死锁
- 资源限制
接下来会逐一分析这三个问题,并给出相应的解决方案。
问题一:上下文切换会带来额外的开销
线程的运行机制
- 一个CPU每个时刻只能执行一条线程;
- 操作系统给每条线程分配不同长度的时间片;
- 操作系统会从一堆线程中随机选取一条来执行;
- 每条线程用完自己的时间片后,即使任务还没完成,操作系统也会剥夺它的执行权,让另一条线程执行
什么是“上下文切换”?
当一条线程的时间片用完后,操作系统会暂停该线程,并保存该线程相应的信息,然后再随机选择一条新线程去执行,这个过程就称为“线程的上下文切换”。
上下文切换的过程
- 暂停正在执行的线程;
- 保存该线程的相关信息(如:执行到哪一行、程序计算的中间结果等)
- 从就绪队列中随机选一条线程;
- 读取该线程的上下文信息,继续执行
上下文切换是有开销的
每次进行上下文切换时都需要保存当前线程的执行状态,并加载新线程先前的状态。
如果上下文切换频繁,CPU花在上下文切换上的时间占比就会上升,而真正处理任务的时间占比就会下降。
因此,为了提高并发程序的执行效率,让CPU把时间花在刀刃上,我们需要减少上下文切换的次数。
如何减少上下文切换?
减少线程的数量
由于一个CPU每个时刻只能执行一条线程,而傲娇的我们又想让程序并发执行,操作系统只好不断地进行上下文切换来使我们从感官上觉得程序是并发执的行。因此,我们只要减少线程的数量,就能减少上下文切换的次数。
然而如果线程数量已经少于CPU核数,每个CPU执行一条线程,照理来说CPU不需要进行上下文切换了,但事实并非如此。控制同一把锁上的线程数量
如果多条线程共用同一把锁,那么当一条线程获得锁后,其他线程就会被阻塞;当该线程释放锁后,操作系统会从被阻塞的线程中选一条执行,从而又会出现上下文切换。
因此,减少同一把锁上的线程数量也能减少上下文切换的次数。采用无锁并发编程
我们知道,如果减少同一把锁上线程的数量就能减少上下文切换的次数,那么如果不用锁,是否就能避免因竞争锁而产生的上下文切换呢?
答案是肯定的!但你需要根据以下两种情况挑选不同的策略:- 需要并发执行的任务是无状态的:HASH分段
所谓无状态是指并发执行的任务没有共享变量,他们都独立执行。对于这种类型的任务可以按照ID进行HASH分段,每段用一条线程去执行。 - 需要并发执行的任务是有状态的:CAS算法
如果任务需要修改共享变量,那么必须要控制线程的执行顺序,否则会出现安全性问题。你可以给任务加锁,保证任务的原子性与可见性,但这会引起阻塞,从而发生上下文切换;为了避免上下文切换,你可以使用CAS算法,
仅在线程内部需要更新共享变量时使用CAS算法来更新,这种方式不会阻塞线程,并保证更新过程的安全性。
- 需要并发执行的任务是无状态的:HASH分段
问题二:并发不当可能会产生死锁
什么是“死锁”?
当多个线程相互等待已经被对方占用的资源时,就会产生死锁。
死锁示例
class DeadLock {
// 锁A
private Object lockA;
// 锁B
private Object lockB;
// 第一条线程
Thread t1 = new Thread(new Runnable(){
void run () {
synchronized (lockA) {
Thread.sleep(5000);
synchronized (lockB) {
System.out.println("线程1");
}
}
}
}).start();
// 第二条线程
Thread t2 = new Thread(new Runnable(){
void run () {
synchronized (lockB) {
Thread.sleep(5000);
synchronized (lockA) {
System.out.println("线程2");
}
}
}
}).start();
}
- 线程1和线程2都需要锁A和锁B
- 线程1首先获得锁A,然后sleep 5秒
PS:线程sleep过程中会释放执行权 - 此时线程2执行,获得锁B,然后也sleep 5秒;
- 线程1 sleep 5秒后继续执行,此时需要锁B,然而锁B已经被线程2持有,因此线程1被阻塞;
- 此时线程2醒了,它需要锁A,然而锁A已经被线程1持有,因此它也被阻塞;
- 此时死锁出现了!两条线程相互等待已经被占用的资源,程序就死在这了。
死锁是并发编程中一个重要的问题,上面介绍的减少上下文切换只是为了提升程序的性能,而一旦产生死锁,程序就不能正确执行!
如何避免死锁?
- 不要在一条线程中嵌套使用多个锁;
- 不要在一条线程中嵌套占用多个计算机资源;
- 给锁和资源加超时时间
如果你非要在一条线程中嵌套使用多个锁或占用多个资源,那你需要给锁、资源加超时时间,从而避免无限期的等待。
问题三:计算机资源会限制并发
误区:线程越多速度越快
在并发编程中,并不是线程越多越好,有时候线程多了反而会拉低执行效率,原因如下:
- 线程多了会导致上下文切换增多,CPU花在上下文切换的时间增多后,花在处理任务上的时间自然就减少了。
- 计算机资源会限制程序的并发度。
- 比如:你家网入口带宽10M,你写了个多线程下载的软件,同时开100条线程下载,那每条线程平均以每秒100k的速度下载,然而100条线程之间还要不断进行上下文切换,所以你还不如只开5条线程,每条平均2M/s的速度下载。
- 再比如:数据库连接池最多给你用10个连接,然而你却开了100条线程进行数据库操作,那么当10个用完后其他线程就要等待,从而操作系统要在这100条线程间不断进行上下文切换;所以与其这样还不如只开10条线程,减少上下文切换的次数。
说了这么多只想告诉你一个道理:线程并不是越多越好,要根据当前计算机所能提供的资源考虑。
什么是“资源”?
资源分为硬件资源和软件资源:
- 硬件资源
- 硬盘读写速度
- 网络带宽
- 等
- 软件资源
- Socket连接数
- 数据库连接数
- 等
如何解决资源的限制?
- 花钱买更高级的机器
- 根据资源限制并发度
Java并发编程的艺术(一)——并发编程需要注意的问题的更多相关文章
- 《Java并发编程的艺术》并发编程的挑战(一)
并发编程的挑战 并发编程的初衷是让程序运行的更快,但是更多的使用多线程真的会让程序变快吗? 1.线程上下文切换 关于线程上下文切换 多个线程在一个处理器里并不是同时进行的,而是非常快速地在线程之间进行 ...
- 《Java并发编程的艺术》并发编程的基础(四)
一.线程简介 1.线程的概念 系统运行的最小单元 2.为何使用多线程 更好地利用系统资源(处理器多核心),提高响应速度. 3.线程的状态 NEW(创建状态) RUNABLE(运行状态,系统调度,争抢时 ...
- 读《Java并发编程的艺术》(一)
离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督 ...
- Java并发编程的艺术读书笔记(2)-并发编程模型
title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...
- Java并发编程的艺术读书笔记(1)-并发编程的挑战
title: Java并发编程的艺术读书笔记(1)-并发编程的挑战 date: 2017-05-03 23:28:45 tags: ['多线程','并发'] categories: 读书笔记 --- ...
- 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结
<Java并发编程实战>和<Java并发编程的艺术> Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...
- Java并发编程的艺术(六)——线程间的通信
多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java ...
- Java并发编程的艺术(三)——volatile
1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...
- java并发编程的艺术(一)---锁的基本属性
本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...
随机推荐
- php $_FILES处理文件上传
众所周知,文件上传在一些网站应用中是必不可少的一部分.比如个人博客上传个性头像,一些论坛分享好的学习资料等,这就涉及到使用表单处理文件上传的知识,在php中 我们可以使用$_FILES这个全局数组来处 ...
- docker 获取容器id
docker ps -aqf 'name=pypaltform2018_v1_trust_pro'
- selenium自动测试
import requestsimport sysimport iofrom selenium import webdriverfrom selenium.webdriver.common.actio ...
- 020 shuffle的重要作用,以及分区的实践
一:学shuffle原理的必要性 1.说明 学习shuffle的作用是可以对程序进行优化. 在shuffle这个部分有三个部分需要注意: 分区 排序 分组 这个可以进行优化. 二:分区的实践 1.说明 ...
- tqdm:Python 进度条
Tqdm 是 Python 进度条库,可以在 Python 长循环中添加一个进度提示信息.用户只需要封装任意的迭代器,是一个快速.扩展性强的进度条工具库. 用法:tqdm(iterator) 代码地址 ...
- 使用Django简单编写一个XSS平台
1) 简要描述 原理十分简单2333,代码呆萌,大牛勿喷 >_< 2) 基础知识 XSS攻击基本原理和利用方法 Django框架的使用 3) Let's start 0x01 ...
- Xamarin iOS教程之页面控件
Xamarin iOS教程之页面控件 Xamarin iOS 页面控件 在iPhone手机的主界面中,经常会看到一排小白点,那就是页面控件,如图2.44所示.它是由小白点和滚动视图组成,可以用来控制翻 ...
- BZOJ.2462.[BeiJing2011]矩阵模板(二维Hash)
题目链接 序列上的Hash和前缀和差不多,二维Hash也和二维前缀和差不多了. 预处理大矩阵所有r*c的小矩阵hash值,再对询问的矩阵Hash. 类比于序列上\(s[r]-s[l-1]*pow[r- ...
- JavaScript基础之运算符及全面的运算符优先级总结
算数运算符: 加+,减—,乘*,除/,求余%,加加++,减减——, 加减乘除求余运算与数学上的用法完全一样. 不过,加号+还有连接字符串的作用,其他运算符还可以将字符串数字转换成数值型,参见JavaS ...
- SGU 200. Cracking RSA (高斯消元求自由变元个数)
题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=200 200. Cracking RSA time limit per test: ...