并发是为了提升程序的执行速度,但并不是多线程一定比单线程高效,而且并发编程容易出错。若要实现正确且高效的并发,就要在开发过程中时刻注意以下三个问题:

  • 上下文切换
  • 死锁
  • 资源限制

接下来会逐一分析这三个问题,并给出相应的解决方案。

问题一:上下文切换会带来额外的开销

线程的运行机制

  • 一个CPU每个时刻只能执行一条线程;
  • 操作系统给每条线程分配不同长度的时间片;
  • 操作系统会从一堆线程中随机选取一条来执行;
  • 每条线程用完自己的时间片后,即使任务还没完成,操作系统也会剥夺它的执行权,让另一条线程执行

什么是“上下文切换”?

当一条线程的时间片用完后,操作系统会暂停该线程,并保存该线程相应的信息,然后再随机选择一条新线程去执行,这个过程就称为“线程的上下文切换”。

上下文切换的过程

  • 暂停正在执行的线程;
  • 保存该线程的相关信息(如:执行到哪一行、程序计算的中间结果等)
  • 从就绪队列中随机选一条线程;
  • 读取该线程的上下文信息,继续执行

上下文切换是有开销的

每次进行上下文切换时都需要保存当前线程的执行状态,并加载新线程先前的状态。
如果上下文切换频繁,CPU花在上下文切换上的时间占比就会上升,而真正处理任务的时间占比就会下降。
因此,为了提高并发程序的执行效率,让CPU把时间花在刀刃上,我们需要减少上下文切换的次数。

如何减少上下文切换?

  • 减少线程的数量
    由于一个CPU每个时刻只能执行一条线程,而傲娇的我们又想让程序并发执行,操作系统只好不断地进行上下文切换来使我们从感官上觉得程序是并发执的行。因此,我们只要减少线程的数量,就能减少上下文切换的次数。
    然而如果线程数量已经少于CPU核数,每个CPU执行一条线程,照理来说CPU不需要进行上下文切换了,但事实并非如此。

  • 控制同一把锁上的线程数量
    如果多条线程共用同一把锁,那么当一条线程获得锁后,其他线程就会被阻塞;当该线程释放锁后,操作系统会从被阻塞的线程中选一条执行,从而又会出现上下文切换。
    因此,减少同一把锁上的线程数量也能减少上下文切换的次数。

  • 采用无锁并发编程
    我们知道,如果减少同一把锁上线程的数量就能减少上下文切换的次数,那么如果不用锁,是否就能避免因竞争锁而产生的上下文切换呢?
    答案是肯定的!但你需要根据以下两种情况挑选不同的策略:

    1. 需要并发执行的任务是无状态的:HASH分段
      所谓无状态是指并发执行的任务没有共享变量,他们都独立执行。对于这种类型的任务可以按照ID进行HASH分段,每段用一条线程去执行。
    2. 需要并发执行的任务是有状态的:CAS算法
      如果任务需要修改共享变量,那么必须要控制线程的执行顺序,否则会出现安全性问题。你可以给任务加锁,保证任务的原子性与可见性,但这会引起阻塞,从而发生上下文切换;为了避免上下文切换,你可以使用CAS算法,
      仅在线程内部需要更新共享变量时使用CAS算法来更新,这种方式不会阻塞线程,并保证更新过程的安全性。

问题二:并发不当可能会产生死锁

什么是“死锁”?

当多个线程相互等待已经被对方占用的资源时,就会产生死锁。

死锁示例

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并发编程的艺术(一)——并发编程需要注意的问题的更多相关文章

  1. 《Java并发编程的艺术》并发编程的挑战(一)

    并发编程的挑战 并发编程的初衷是让程序运行的更快,但是更多的使用多线程真的会让程序变快吗? 1.线程上下文切换 关于线程上下文切换 多个线程在一个处理器里并不是同时进行的,而是非常快速地在线程之间进行 ...

  2. 《Java并发编程的艺术》并发编程的基础(四)

    一.线程简介 1.线程的概念 系统运行的最小单元 2.为何使用多线程 更好地利用系统资源(处理器多核心),提高响应速度. 3.线程的状态 NEW(创建状态) RUNABLE(运行状态,系统调度,争抢时 ...

  3. 读《Java并发编程的艺术》(一)

    离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督 ...

  4. Java并发编程的艺术读书笔记(2)-并发编程模型

    title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...

  5. Java并发编程的艺术读书笔记(1)-并发编程的挑战

    title: Java并发编程的艺术读书笔记(1)-并发编程的挑战 date: 2017-05-03 23:28:45 tags: ['多线程','并发'] categories: 读书笔记 --- ...

  6. 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结

    <Java并发编程实战>和<Java并发编程的艺术>           Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...

  7. Java并发编程的艺术(六)——线程间的通信

    多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java ...

  8. Java并发编程的艺术(三)——volatile

    1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...

  9. java并发编程的艺术(一)---锁的基本属性

    本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...

随机推荐

  1. 1.网站应用程序 - 《APS.NET本质论》

    1.1.HTTP协议 浏览器与WEB服务器的协议是应用层协议,当前遵循HTTP/1.1,HTTP协议是无状态的协议 客户机与服务器通过请求和响应完成一次会话(Session),每次会话中,双方发送的数 ...

  2. codewar 上做练习的一些感触

    废话 在[codewar][1]上做练习,每次都是尽量快速地做完,然后赶着去看排名里面clever分最高的solution,看完每次都要感叹一下人家怎么可以写得这么简洁,甚至有一次我用了一段大约七八行 ...

  3. http://blog.csdn.net/five3/article/details/7181521

    首先来了解什么是multipart/form-data请求: 根据http/1.1 rfc 2616的协议规定,我们的请求方式只有OPTIONS.GET.HEAD.POST.PUT.DELETE.TR ...

  4. 喊山 BFS

    一个山头呼喊的声音可以被临近的山头同时听到.题目假设每个山头最多有两个能听到它的临近山头.给定任意一个发出原始信号的山头,本题请你找出这个信号最远能传达到的地方. 输入格式: 输入第一行给出3个正整数 ...

  5. 如何删除 Win10此电脑 desktop 视频 图片 文档 下载 音乐等文件夹

    更改注册表; 1. WIN+R   输入[regedit]  打开注册表管理器 2. 找到路径[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Curren ...

  6. DSP 中关键字extern,cregister,Near ,Far,restrict,volatile

    extern:extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.另外,extern也可用来进行链接指定. const: 可以 ...

  7. MySQL数据库引擎详解

    作为Java程序员,MySQL数据库大家平时应该都没少使用吧,对MySQL数据库的引擎应该也有所了解,这篇文章就让我详细的说说MySQL数据库的Innodb和MyIASM两种引擎以及其索引结构.也来巩 ...

  8. [转]01分数规划算法 ACM 二分 Dinkelbach 最优比率生成树 最优比率环

    01分数规划 前置技能 二分思想最短路算法一些数学脑细胞? 问题模型1 基本01分数规划问题 给定nn个二元组(valuei,costi)(valuei,costi),valueivaluei是选择此 ...

  9. JAVAEE——SpringBoot日志篇:日志框架SLF4j、日志配置、日志使用、切换日志框架

    Spring Boot 日志篇 1.日志框架(故事引入) 小张:开发一个大型系统: ​ 1.System.out.println(""):将关键数据打印在控制台:去掉?写在一个文件 ...

  10. Codeforces.835E.The penguin's game(交互 按位统计 二分)

    题目链接 \(Description\) 有一个长为\(n\)的序列,其中有两个元素为\(y\),其余全为\(x\).你可以进行\(19\)次询问,每次询问你给出一个下标集合,交互库会返回这些元素的异 ...