Page cover image

第三章 线程

3.1 线程的基本概念

1.什么是线程?为什么要引入线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。引入线程的原因包括:

  1. 提高并发性:线程可以独立执行,使得一个程序可以同时执行多个任务,提高程序的并发性能。

  2. 资源共享:同一进程内的线程共享进程的资源,如内存和文件描述符,这减少了资源管理的开销。

  3. 响应性:线程的创建、切换和销毁比进程更快,可以提供更快的响应时间。

  4. 简化编程模型:线程简化了并发编程的模型,使得程序设计更加模块化。

2.引入线程机制后,有什么变化?

  1. 资源管理:线程共享进程资源,减少了资源分配和管理的复杂性。

  2. 调度:线程的调度通常比进程调度更加频繁,操作系统需要更高效的调度算法。

  3. 同步:线程间的同步和通信机制需要更加精细,以避免竞态条件和死锁。

  4. 上下文切换:线程的上下文切换比进程的上下文切换开销小。

  5. **调度单位:**线程是一个基本的CPU执行单元,也是程序执行流的最小单位。进程只作为除CPU之外的系统资源的分配单位。(如打印机,内存地址空间等)

3.线程有哪些重要的属性?

  1. 线程ID:唯一标识一个线程。

  2. 程序计数器:记录下一条要执行的指令。

  3. 寄存器集合:保存线程的当前工作状态。

  4. 堆栈:用于存放局部变量和执行函数调用。

  5. 状态:线程可以处于不同的状态,如新建、就绪、运行、阻塞、终止等。

3.2 线程的实现方式和多线程模型

1.线程的实现方式

  1. 用户级线程(User-Level Threads,ULTs)

    • 由用户空间的线程库管理,不依赖于操作系统的内核。

    • 调度算法由线程库实现。

    • 上下文切换快,不涉及内核态与用户态的切换。

  2. 内核级线程(Kernel-Level Threads,KLTs)

    • 由操作系统内核管理。

    • 调度由操作系统负责。

    • 可以利用多处理器,但上下文切换开销较大。

2.多线程模型

  1. 多对一模型(Many-to-One)

    • 多个用户级线程映射到一个内核级线程。

    • 优点是线程管理简单,上下文切换快。

    • 缺点是如果一个线程执行系统调用阻塞,整个进程都会被阻塞。

  2. 一对一模型(One-to-One)

    • 每个用户级线程映射到一个内核级线程。

    • 可以利用多处理器,一个线程的阻塞不会影响其他线程。

    • 缺点是创建和管理的开销大。

  3. 多对多模型(Many-to-Many)

    • 多个用户级线程映射到多个内核级线程。

    • 结合了多对一和一对一的优点,提供了更好的并发性和响应性。

    • 管理复杂,需要线程库和内核之间良好的协作。

线程和多线程模型的选择取决于应用程序的需求、目标平台和操作系统的支持。

3.3 线程的状态与转换

线程的状态与转换

线程在其生命周期内可以处于以下几种状态,并且可以在一定条件下相互转换:

  1. 新建(New)

    • 状态描述:当使用 new 关键字创建一个线程后,直到调用 start() 方法之前,线程处于新建状态。

    • 转换条件:调用 start() 方法。

  2. 就绪(Runnable)

    • 状态描述:线程调用 start() 方法后,进入就绪状态。就绪状态的线程位于可运行池中,等待被线程调度器选中获取CPU的执行时间。

    • 转换条件:线程调度器选中该线程。

  3. 运行(Running)

    • 状态描述:线程获得CPU时间,执行程序代码。

    • 转换条件:时间片用完;更高优先级的线程需要运行;线程自己调用 yield() 方法;线程被调度器终止。

  4. 阻塞(Blocked)

    • 状态描述:线程因为某些原因放弃CPU,暂时停止运行。阻塞状态是线程阻塞在进入同步代码块/方法或等待某个条件发生。

    • 转换条件:获取锁;等待的条件成立。

  5. 等待(Waiting)

    • 状态描述:线程等待其他线程执行特定操作(通知)。

    • 转换条件:其他线程发出通知。

  6. 超时等待(Timed Waiting)

    • 状态描述:线程在一定时间内等待另一个线程的通知。

    • 转换条件:时间到达;其他线程发出通知。

  7. 终止(Terminated)

    • 状态描述:线程的 run() 方法执行完成,或者线程调用了 stop() 方法,或者因为一个未捕获的异常导致线程终止。

    • 转换条件:线程执行结束。

线程的组织与控制

线程的组织与控制主要涉及以下几个方面:

  • 线程控制块(TCP):

    • 线程标识符:TID

    • 程序计数器PC:线程目前执行到哪里

    • 其他寄存器:线程运行的中间结果

    • 堆栈指针:堆栈保存函数调用信息,局部变量等

    • 线程运行状态:运行/就绪/阻塞

    • 优先级:线程调度,资源分配参考

  1. 线程的创建

    • 继承 Thread 类并重写 run() 方法。

    • 实现 Runnable 接口的 run() 方法。

    • 使用 Executor 框架创建线程池。

  2. 线程的调度

    • 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

    • 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,Java 默认采用抢占式调度。

  3. 线程的控制

    • 线程睡眠:Thread.sleep(long millis),让线程暂停执行指定的时间。

    • 线程让步:Thread.yield(),暂停当前正在执行的线程对象,并执行其他线程。

    • 线程加入:join(),等待其他线程终止。

    • 线程中断:interrupt(),中断线程的阻塞状态。

  4. 线程同步

    • 使用 synchronized 关键字实现同步方法或同步代码块。

    • 使用 Lock 显示锁控制临界区。

  5. 线程通信

    • 使用 Object 类的 wait()、notify()、notifyAll() 方法进行线程通信。

  6. 线程池管理

    • 通过 Executors 工厂类创建不同类型的线程池。

    • 使用 ThreadPoolExecutor 自定义线程池。

线程的正确使用和管理对于提高程序的性能和稳定性至关重要。在多线程编程中,需要注意线程安全、资源共享和合理利用系统资源。

Last updated