今天开始,这个专题主要讲解 java 并发编程系列,从操作系统的进程、CPU、线程等底层开始, 到java 并发工具类,线程、线程池,并发容器、CAS、AQS等原理、源码和使用均做深入的解析。今天讲 进程、线程和CPU 之间的关系 。

1、基础概念 什么是进程和线程 ?

       进程是程序运行资源分配的最小单位 。进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、 磁盘 IO 等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程 之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次 运行活动,进程是系统进行资源分配和调度的一个独立单位。 进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一 个进程。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进 程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就 是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。

       线程是 CPU 调度的最小单位,必须依赖于进程而存在 线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的、 能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中 必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其 他的线程共享进程所拥有的全部资源。 线程无处不在 任何一个程序都必须要创建线程,特别是 Java 不管任何程序都必须启动一个 main 函数的主线程; Java Web 开发里面的定时任务、定时器、JSP 和 Servlet、异 步消息处理机制,远程访问接口RM等,任何一个监听事件, onclick的触发事件等都 离不开线程和并发的知识。
 2、CPU 核心数和线程数的关系

        多核心:也指单芯片多处理器( Chip Multiprocessors,简称 CMP),CMP 是由美国 斯坦福大学提出的,其思想是将大规模并行处理器中的 SMP(对称多处理器)集成 到同一芯片内,各个处理器并行执行不同的进程。这种依靠多个 CPU 同时并行地 运行程序是实现超高速计算的一个重要方向,称为并行处理 多线程: Simultaneous Multithreading.简称 SMT.让同一个处理器上的多个线 程同步执行并共享处理器的执行资源。 核心数、线程数:目前主流 CPU 都是多核的。增加核心数目就是为了增加线 程数,因为操作系统是通过线程来执行任务的,一般情况下它们是 1:1 对应关系,也 就是说四核 CPU 一般拥有四个线程。但 Intel 引入超线程技术后,使核心数与线程 数形成 1:2 的关系

 

3、CPU 时间片轮转机制

          我们平时在开发的时候,感觉并没有受 cpu 核心数的限制,想启动线程就启 动线程,哪怕是在单核 CPU 上,为什么?这是因为操作系统提供了一种 CPU 时 间片轮转机制。 时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称 RR 调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。 百度百科对 CPU 时间片轮转机制原理解释如下: 如果在时间片结束时进程还在运行,则 CPU 将被剥夺并分配给另一个进程。 如果进程在时间片结束前阻塞或结来,则 CPU 当即进行切换。调度程序所要做的 就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾 时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一 个进程是需要定时间的,包括保存和装入寄存器值及内存映像,更新各种表格和队列等。假如进程切( processwitch),有时称为上下文切换( context switch),需要 5ms, 再假设时间片设为 20ms,则在做完 20ms 有用的工作之后,CPU 将花费 5ms 来进行 进程切换。CPU 时间的 20%被浪费在了管理开销上了。 为了提高 CPU 效率,我们可以将时间片设为 5000ms。这时浪费的时间只有 0.1%。但考虑到在一个分时系统中,如果有 10 个交互用户几乎同时按下回车键, 将发生什么情况?假设所有其他进程都用足它们的时间片的话,最后一个不幸的 进程不得不等待 5s 才获得运行机会。多数用户无法忍受一条简短命令要 5 才能 做出响应,同样的问题在一台支持多道程序的个人计算机上也会发

           结论可以归结如下:时间片设得太短会导致过多的进程切换,降低了 CPU 效率: 而设得太长又可能引起对短的交互请求的响应变差。将时间片设为 100ms 通常 是一个比较合理的折衷。 在 CPU 死机的情况下,其实大家不难发现当运行一个程序的时候把 CPU 给弄 到了 100%再不重启电脑的情况下,其实我们还是有机会把它 KⅢ掉的,我想也正是 因为这种机制的缘故。

 4、澄清并行和并发

         我们举个例子,如果有条高速公路 A 上面并排有 8 条车道,那么最大的并行车 辆就是 8 辆此条高速公路 A 同时并排行走的车辆小于等于 8 辆的时候,车辆就可 以并行运行。CPU 也是这个原理,一个 CPU 相当于一个高速公路 A,核心数或者线 程数就相当于并排可以通行的车道;而多个 CPU 就相当于并排有多条高速公路,而 每个高速公路并排有多个车道。 当谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少? 离开了单位时间其实是没有意义的。 俗话说,一心不能二用,这对计算机也一样,原则上一个 CPU 只能分配给一个 进程,以便运行这个进程。我们通常使用的计算机中只有一个 CPU,也就是说只有 一颗心,要让它一心多用同时运行多个进程,就必须使用并发技术。实现并发技术 相当复杂,最容易理解的是“时间片轮转进程调度算法”。

             综合来说: 并发:指应用能够交替执行不同的任务,比如单 CPU 核心下执行多线程并非是 同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不 断去切换这两个任务,已达到"同时执行效果",其实并不是的,只是计算机的速度太 快,我们无法察觉到而已.

             并行:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行

            两者区别:一个是交替执行,一个是同时执行. 

 
5、高并发编程的意义、好处和注意事项
由于多核多线程的 CPU 的诞生 , 多线程、高并发的编程越来越受重视和关注。 多线程可以给程序带来如下好处。
(1) 充分利用 CPU 的资源
          从上面的 CPU 的介绍 , 可以看的出来 , 现在市面上没有 CPU 的内核不使用多线 程并发机制的, 特别是服务器还不止一个 CPU, 如果还是使用单线程的技术做思路 ,
明显就 out 了。因为程序的基本调度单元是线程 , 并且一个线程也只能在一个 CPU 的一个核的一个线程跑, 如果你是个 i3 CPU 的话 , 最差也是双核心 4 线程的运算 能力: 如果是一个线程的程序的话 , 那是要浪费 3/4 CPU 性能 : 如果设计一个多线 程的程序的话, 那它就可以同时在多个 CPU 的多个核的多个线程上跑 , 可以充分地 利用 CPU, 减少 CPU 的空闲时间 , 发挥它的运算能力 , 提高并发量。 就像我们平时坐地铁一样, 很多人坐长线地铁的时候都在认真看书 , 而不是为 了坐地铁而坐地铁, 到家了再去看书 , 这样你的时间就相当于有了两倍。这就是为 什么有些人时间很充裕, 而有些人老是说没时间的一个原因 , 工作也是这样 , 有的 时候可以并发地去做几件事情, 充分利用我们的时间 ,CPU 也是一样 , 也要充分利用。
          (2) 加快响应用户的时间
比如我们经常用的迅雷下载 , 都喜欢多开几个线程去下载 , 谁都不愿意用一个 线程去下载, 为什么呢 ? 答案很简单 , 就是多个线程下载快啊。 我们在做程序开发的时候更应该如此, 特别是我们做互联网项目 , 网页的响应 时间若提升 1s, 如果流量大的话 , 就能增加不少转换量。做过高性能 web 前端调优 的都知道, 要将静态资源地址用两三个子域名去加载 , 为什么 ? 因为每多一个子域 名, 浏览器在加载你的页面的时候就会多开几个线程去加载你的页面资源 , 提升网 站的响应速度。多线程, 高并发真的是无处不在。
          (3) 可以使你的代码模块化 , 异步化 , 简单化 例如我们实现电商系统,下订单和给用户发送短信、邮件就可以进行拆分, 将给用户发送短信、邮件这两个步骤独立为单独的模块,并交给其他线程去执行。 这样既增加了异步的操作,提升了系统性能,又使程序模块化,清晰化和简单化。 多线程应用开发的好处还有很多, 大家在日后的代码编写过程中可以慢慢体 会它的魅力。
6、多线程程序需要注意事项
(1) 线程之间的安全性
      从前面的章节中我们都知道 , 在同一个进程里面的多线程是资源共享的 , 也就 是都可以访问同一个内存地址当中的一个变量。例如: 若每个线程中对全局变量、 静态变量只有读操作, 而无写操作 , 一般来说 , 这个全局变量是线程安全的 : 若有多 个线程同时执行写操作, 一般都需要考虑线程同步 , 否则就可能影响线程安全。
(2) 线程之间的死锁
     为了解决线程之间的安全性引入了 Java 的锁机制 , 而一不小心就会产生 Java 线程死锁的多线程问题, 因为不同的线程都在等待那些根本不可能被释放的锁 , 从 而导致所有的工作都无法完成。假设有两个线程, 分别代表两个饥饿的人 , 他们必 须共享刀叉并轮流吃饭。他们都需要获得两个锁: 共享刀和共享叉的锁。 假如线程 A 获得了刀 , 而线程 B 获得了叉。线程 A 就会进入阻塞状态来等待 获得叉, 而线程 B 则阻塞来等待线程 A 所拥有的刀。这只是人为设计的例子 , 但尽 管在运行时很难探测到, 这类情况却时常发生
(3) 线程太多了会将服务器资源耗尽形成死机当机
        线程数太多有可能造成系统创建大量线程而导致消耗完系统内存以及 CPU 的“过渡切换”, 造成系统的死机 , 那么我们该如何解决这类问题呢 ? 某些系统资源是有限的, 如文件描述符。多线程程序可能耗尽资源 , 因为每个 线程都可能希望有一个这样的资源。如果线程数相当大, 或者某个资源的侯选线 程数远远超过了可用的资源数则最好使用资源池。一个最好的示例是数据库连接 池。只要线程需要使用一个数据库连接, 它就从池中取出一个 , 使用以后再将它返 回池中。资源池也称为资源库。 多线程应用开发的注意事项很多,希望大家在日后的工作中可以慢慢体会它 的危险所在。
       这篇我们大概说一下并发编程的基本概念,后面我会通过代码详细分析,敬请期待!

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐