0%

Java复健系列(5):Java多线程

1. 多线程

  • 程序 是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
  • 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
  • 线程 与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

2. 进程

2.1. 定义

狭义定义:进程是计算机中正在运行的程序的实例(an instance of a computer program that is being executed)。

程序本身只是指令的集合,进程才是程序(那些指令)的真正运行。用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相互冲突。进程需要一些资源才能完成工作,比如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是任何时间内仅能运行一项进程。

2.2. 基本状态

通常进程有如下5种状态,其中前三种是进程的基本状态。

  1. 运行状态(执行窗台):进程正在处理器上运行。在单处理器环境下,每一时刻最多只有一个进程处于运行状态。
  2. 就绪状态:进程已处于准备运行的状态,即进程获得了除处理器之外的一切所需资源,一旦得到处理器即可运行。
  3. 阻塞状态:又称为等待状态,进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理器)或等待输入/输出完成。即使处理器空闲,该进程也不能运行。
  4. 创建状态:进程正在被创建,尚未转到就绪状态。
  5. 结束状态:进程正从系统中消失。可能是进程正常结束或其他原因中断退出运行。
  • 当一个就绪状态获得处理机时,其状态由就绪变为执行;
  • 当一个运行进程被剥夺处理机时,如用完系统分给它的时间片、出现更高优先级别的其他进程,其状态由运行变为就绪;
  • 当一个运行进程因某事件受阻时,如所申请资源被占用、启动I/O传输未完成,其状态由执行变为阻塞;
    当所等待事件发生时,如得到申请资源、I/O传输完成,其状态由阻塞变为就绪

2.3. 进程与程序的区别

  • 进程是程序及其数据在计算机上的一次运行活动,是一个动态的概念。进程的运行实体是程序、离开程序的进程没有存在的意义。从静态角度看,进程是由程序、数据和进程控制块(PCB)三部分组成的。而程序时一组有序的指令集合,是一种静态的概念。
  • 进程是程序的一次执行过程,它是动态地创建和消亡的,具有一定的生命期,是暂时存在的;而程序则是一组代码的集合,它是永久存在的,可长期保存。
  • 一个进程可以执行一个或几个程序,一个程序也可以构成多个进程。进程可创建进程,而程序不可能形成新的程序。

3. 线程

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈(stack)组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的资源。

线程共享的进程环境包括:进程代码段、进程的共有数据(如全局变量,利用这些共享的数据,线程很容易的实现相互之间的通信)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。

线程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能够实现并发性。这些个性包括:

  • 线程ID:每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程;
  • 寄存器的值:由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有线程的寄存器集合的状态进行保存,以便将来该线程在被重新切换时能得以恢复。
  • 线程的堆栈(Stack):堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。在一个进程的线程共享堆区(heap)。
  • 错误返回码
  • 线程的信号屏蔽码
  • 线程的优先级

一个线程可以创建和撤销另一个线程,同一进程的多个线程之间可以并发执行。由于县城之间的相互制约,致使线程在运行中呈现间断性。

线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

引入线程后,进程的内涵发生了变化,进程只作为除CPU以外系统资源的分配单元,线程则作为处理器的分配单元。在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。

3.1. 线程的基本状态

状态名称 说明
NEW 初始状态,线程被构建,但还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪与运行两种状态都称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,当前线程需要等待其他线程做出特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不等于WAITING,可以在指定时间自动返回
TERMINATED 终止状态,当前线程已经执行完毕

线程状态变迁如下所示

线程状态

3.2. 进程与线程的区别

  • 调度:在传统操作系统中,拥有资源和独立调度的基本单位都是进程。引入线程后,线程是独立调度的基本单位,进程是拥有资源的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行的线程切换,则会引起进程切换。
  • 拥有资源:不论是传统的还是引入线程的操作系统,进程都是拥有资源的基本单位,线程不拥有资源(也有一点必不可少的资源),但线程可以共享其隶属进程的系统资源。
  • 并发性:在引入线程的操作系统中,不仅进程可以并发执行,而且同一进程内的多个线程也可以并发执行,从而使操作系统具有具有更好的并发性,大大提高了系统的吞吐量。
  • 系统开销:创建和撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O设备等等,因此操作系统所付出的开销远大于创建或撤销线程的开销。类似地,在进程切换时,涉及当前执行进程CPU环境的保存以及新调度的进程CPU环境的设置;而线程切换时只需保存和设置少量寄存器内容,因此开销很小。另外,由于同一进程内的多个线程共享进程的地址空间,因此这些线程之间的同步与通信比较容易实现,甚至无需操作系统的干预。
  • 地址空间和其他资源(如打开的文件):进程的地址空间之间相互独立,同一进程的各线程间共享进程的资源,某进程内的线程对于其他进程不可见。
  • 通信方面:进程间通信需要借助操作系统,而线程间可以直接读、写进程数据段(如全局变量)来进行通信。

4. 进程通信与同步

多个进程可以共享系统中的各种资源,但其中许多资源一次为能为一个进程使用,我们把一次仅允许一个进程使用的资源成为临界资源。许多物理设备都属于临界资源,如打印机等。

对临界资源的访问,必须互斥的进行,在每个进程中,访问临界资源的那段代码成为临界区(Critical Section)。

进程通信与同步有如下一些目的。

  • 数据传输
  • 共享数据
  • 通知数据
  • 资源共享
  • 进程控制

Linux进程间通信的几种主要手段简介:

  • 管道(Pipe)及有名管道(named Pipe)

  • 信号(Signal)

  • Message(消息队列)

  • 共享内存(Shared Memory)

  • 信号量

  • 套接口

  • Linux线程间通信:互斥体(互斥量)、信号量、条件变量

  • Windows进程间通信:管道、共享内存、消息队列、信号量、socket

  • windows线程间通信:临界区(Critical Section)、互斥量(Mutex)、信号量(信号灯)(Semaphore)、事件(Event)。

5. 调度算法

调度的基本准则包括CPU利用率、系统吞吐量、周转时间、等待时间、响应时间等。

  • 系统吞吐量:表示单位时间内CPU完成作业的数量
  • 周转时间:作业完成时刻减去作业到达的时刻
  • 等待时间:进程处于等处理器状态的时间之和,等待时间越长,用户满意度越低。
  • 响应时间:从用户提交请求到系统首次产生响应所用的时间。

典型的调度算法包括:

  • 实时系统中:FIFO(First Input First Output,先进先出算法),SJF(Shortest Job First,最短作业优先算法),SRTF(Shortest Remaining Time First,最短剩余时间优先算法)。
  • 交互式系统中:RR(Round Robin,时间片轮转算法),HPF(Highest Priority First,最高优先级算法),多级队列,最短进程优先,保证调度,彩票调度,公平分享调度。

6. 死锁

所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。现实生活中简单的例子:交通阻塞,两股相向而行的车流都想通过已被对方占用的道路,结果双方都不能前进。

死锁产生的原因:系统资源的竞争、进程推进顺序非法

死锁产生的必要条件:产生死锁必须同时满足以下四个条件,只要其中任一条件不满足,死锁就不会发生。

  • 互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待;
  • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他资源强行夺走,即只能由获得该资源的进程自己来释放。
  • 请求和保持条件:又称为部分分配条件。进程每次申请它所需要的一部分资源,在等待新资源的同时,进程继续占有已分配到的资源。
  • 循环等待条件:存在一种进程资源的循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{P1,P2,….Pn}其中Pi等待的资源被Pi+1(i=0,1,2,…n−1)占有,Pn等待资源被P0占有。

死锁处理:

  • 预防死锁:设置某些限制条件,破坏产生死锁的四个必要条件中的一个或几个
  • 避免死锁:在资源的动态分配过程中,用某种方法防止系统进入不安全状态。银行家算法是著名的死锁避免算法。
  • 死锁的检测及解除:无需采取任何限制性措施,允许进程在运行过程中发生死锁,通过系统的检测机制及时地检测出死锁的发生,然后采取某种措施解除死锁。死锁可利用资源分配图来描述。死锁的解除主要方法:资源剥夺法、撤销进程法、进程回退法