• 那是从何处传来的钟声呢?偶尔听到那钟声,平添一份喜悦与向往之情。

并发基础知识(一)

后端 Nanait 6年前 (2018-04-16) 894次浏览 已收录 0个评论 扫描二维码
“线程的基本概念”

前言

线程是 java 语言中不可或缺的重要功能,它们能使复杂的异步代码变得非常简单,从而极大的简化了复杂系统的开发。此外,想要充分的发挥多处理器系统的强大计算能力,最简单的方式就是使用线程。

随着处理器数量的持续增长,如何高效地使用并发正变得越来越重要。下面就和博客主一起学习并发编程吧。


正文

首先需要了解的几个相关概念

  • 同步
    同步是指:在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。 换句话说,就是由调用者主动等待这个调用的结果。
    举个例子:比如你早上起床、穿衣、下楼、喝早茶,在喝早茶的时候需要烧水,你坐在水壶旁边等水烧开进行下一步操作。这就是一个同步的过程。
  • 异步
    而异步是指:在调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
    举个例子:同样是早上起床、穿衣、下楼、喝早茶,在喝早茶的时候需要烧水,而在烧水的同时你去刷牙,等水壶发出声音提醒你水烧开了你在去拿水进行下一部操作。这就是一个异步的过程。
  • 同步阻塞
    还是上面那个例子: 当你早上起床、穿衣、下楼、喝早茶,在喝早茶的需要烧水的时候,你用水壶烧水,并且站在那里,不管水开没开,你都每隔一定时间去看看。
  • 同步非阻塞
    当你早上起床、穿衣、下楼、喝早茶,在喝早茶的需要烧水的时候,你不再傻傻的站在那里看水有没有开,而是去玩游戏,但是你还是会每隔一段时间过来看看水开了没有,没有开的话继续玩游戏。
  • 异步阻塞
    当你早上起床、穿衣、下楼、喝早茶,在喝早茶的需要烧水的时候,你站在那里,但不会每隔一段时间去看水开,而是等水开了,水壶会自动提醒你。
  • 异步非阻塞
    当你早上起床、穿衣、下楼、喝早茶,在喝早茶的时候需要烧水,而在烧水的同时你去玩游戏,等水壶发出声音提醒你水烧开了。
  • 并发
    举个例子: 当你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发
    并发的关键是你有处理多个任务的能力,不一定要同时。
  • 并行
    并行与并发相似,但多指不同程序同时在多个硬件部件上执行。它拥有硬件的多个部件的支持。
    同样举个例子: 当你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。并行的关键是你有同时处理多个任务的能力。

下面我们正式切入主题

为什么要编写并发程序(多线程有什么用)?

  • 资源利用率: 在某些情况下,程序必须等待某个外部操作执行完成(例如输入输出操作),而在等待时程序无法执行其他任何工作。因此,如果在等待的同时可以运行另一个程序,那么毫无疑问将提高资源的利用率。
  • 公平性: 不同的用户和程序对与计算机上的资源有着同等的使用权。一种高效的运行方式是通过粗粒度的时间分片使这些用户和程序能共享计算机资源,而不是由一个程序从头运行到尾,然后再启动下一个程序。
  • 便利性: 传统的串行编程模型的优势在于其直观性和简单性,因为它模仿了人类的工作方式:每次只做一件事情,做完之后再做另一件事情。比如,起床、穿衣、下楼、喝早茶。在编程语言中,这些现实世界中的动作可以被进一步抽象为一组粒度更细的动作。例如,喝早茶的动作可以被进一步细化为:打开橱柜,挑选喜欢的茶叶,将一些茶叶倒入杯中,看看茶壶中是否有足够的水,如果没有的话加些水,将茶壶放到火炉上,点燃火炉,然后等水烧开等等。在最后一步等水烧开的过程中包含一定程度的异步性。当正在烧水时,你可以干等着,也可以做些其他事情,比如热一下早饭(这是另一个异步任务)或者刷牙,同时留意茶壶水是否烧开。茶壶和微波炉的产商都很清楚:用户通常会采用异步的方式来使用它们的产平,因此完成任务时都会发出声音提升。但凡做事高效的人,总能在串行性和异步性之间找到合理的平衡,对于程序来说同样如此。


多线程的优势

  1. 发挥多处理器的强大能力
    由于 CPU 的基本调度单位是线程,因此如果在程序中只有一个线程,那么最多同时只能在一个处理器上运行。在双处理器上,单线程的程序永远只能使用一般的 PUC 资源,如果在拥有 100 个处理器的系统上,那么将有 99%的资源无法使用。而多线程程序可以同时在多个处理器上执行。如果设计正确,多线程程序可以通过提高处理器资源的利用率从而提升系统的吞吐量。
  2. 建模的简单性
    使用多线程,对大任务进行拆分,使用异步架构解耦,各自独立发展,便于建模。比如说一个支付系统,它要完成三项工作:1、根据用户的订单后台扣费 2、发送短信给用户提示用户消费成功 3、在某张表中记录用户此次消费行为,以便分析用户的消费习惯,引导用户消费,这是大数据时代会常做的一件事情。假如这三项工作每项工作都包含很多细节,放在单线程里面操作,那么这条线程要考虑的内容就非常多,整个代码流程会变得很复杂。但是用多线程就不一样了,使用多线程可以将此次的行为划分为三部分任务:1、扣费任务 2、发短信任务 3、记录用户消费任务。每部分任务一条线程执行,只需要保证发短信任务和记录用户消费任务在用户扣费任务成功之后就可以了。这三项任务可以分别交由三个不同的开发者进行开发,由于将一个大的任务拆分成了几个小的任务,因此对每部分小任务来说建模、整理流程就变得简单了。
  3. 异步事件的简化处理
    比如 Web 服务器工作方式:首先从客户端接受网页请求、然后再从磁盘上检索相关网页,读入内存、然后将网页返回给客户端。如果没有使用多线程的话,那么在第二步对磁盘进行操作的时候会使得进程暂时停止不能运行,因为要到磁盘上去进行搜索,当磁盘搜索完成把内容读入内存后进程才会继续执行,并且一次只能执行一个客户端发送过来的请求。而使用多线程的话,会由一个分派线程监听客户端发送过来的请求,然后把这个请求分派给其它某一个工作线程来完成,并且可以一次执行多个客户端发送过来的请求。


使用多线程代价

  1. 使得设计更加复杂
    虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往往非常复杂。不正确的线程同步产生的错误非常难以被发现,并且重现以修复。
  2. 上下文切换的开销
    CPU 运行是有时钟周期的,一个时钟周期只能运行一个线程,所以对于多线程程序,需要进行线程的切换和调度(CPU 从一个执行线程切换到另一个执行线程),这个过程 CPU 需要保存现场信息(比如存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等),最后才开始执行。这个过程称为“上下文切换”。
  3. 增加资源消耗
    在多线程编程中,线程需要一些内存在维持线程本地栈,每个线程都有本地独立的栈用以存储线程专用数据,需要内存开销自然就更大了。同步操作导致的开销也是一个重要方面。

来源:luob 的博客文章


何处钟 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:并发基础知识(一)
喜欢 (0)
[15211539367@163.com]
分享 (0)

您必须 登录 才能发表评论!