跳到主要内容

多线程的操作原则

1. 前言

本节内容主要是对多线程的操作原则程进行讲解,具体内容点如下:

  • 了解多线程 AVO 原则,是学习本节内容的基础;
  • 了解单 CPU 时代的多线程,能够更好地理解多 CPU 诞生的原因;
  • 了解多 CPU 时代的多线程,是目前我们接触和使用到的开发场景;
  • 了解为什么要进行多线程并发,使我们学习本套课程的前提,也是多线程并发的意义所在;
  • 了解线程安全问题,这是本节课程的重点,安全问题是开发过程中非常重要且不容忽视的问题;
  • 了解共享变量内存可见性问题,是本节的重点之一。

2. 多线程 AVO 原则

A:即 Atomic,原子性操作原则。对基本数据类型的变量读和写是保证原子性的,要么都成功,要么都失败,这些操作不可中断。

V:即 volatile,可见性原则。后续的小节会对 volatile 关键字进行深入的讲解,此处只需要理解概念性问题即可。使用 volatile 关键字,保证了变量的可见性,到主存拿数据,不是到缓存里拿。

O:即 order, 就是有序性。代码的执行顺序,在代码编译前的和代码编译后的执行顺序不变。

3. 单 CPU 时代的多线程

概念:单核 CPU 上,同一时刻只能有一条线程运行,单核 CPU 上运行的单线程程序和多线程程序,从运行效率上看没有差别。换而言之,单 CPU 时代,没有真正的多线程并发效果,从这一点来看,多线程与 CPU 硬件的升级息息相关。

单 CPU 时代下的多线程:在单 CPU 时代多任务是共享一个 CPU 的,当一个任务占用 CPU 运行时,其他任务就会被挂起,当占用 CPU 的任务时间片用完后,会把 CPU 让给其他任务来使用,所以在单 CPU 时代多线程编程是没有太大意义的,并且线程间频繁的上下文切换还会带来额外开销。

图片描述

上图所示为在单个 CPU 上运行两个线程,线程 A 和线程 B 是轮流使用 CPU 进行任务处理的,也就是在某个时间内单个 CPU 只执行一个线程上面的任务。当线程 A 的时间片用完后会进行线程上下文切换,也就是保存当前线程 A 的执行上下文,然后切换到线程 B 来占用 CPU 运行任务。

4. 多 CPU 时代的多线程

如下图所示为双 CPU 配置,线程 A 和线程 B 各自在自己的 CPU 上执行任务,实现了真正的并行运行。

图片描述

在多线程编程实践中,线程的个数往往多于 CPU 的个数,所以一般都称多线程并发编程而不是多线程并行编程。

5. 为什么要进行多线程并发

意义:多核 CPU 时代的到来打破了单核 CPU 对多线程效能的限制。 多个 CPU 意味着每个线程可以使用自己的 CPU 运行,这减少了线程上下文切换的开销。

但随着对应用系统性能和吞吐量要求的提高,出现了处理海量数据和请求的要求,这些都对高并发编程有着迫切的需求。

6. 线程安全问题

谈到线程安全问题,我们先说说什么是共享资源。

共享资源:所谓共享资源,就是说该资源被多个线程所持有或者说多个线程都可以去访问该资源。线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果和问题。

对于线程安全问题,在进行实际的开发操作过程中,我们要分析一下几点内容,确保多线程环境下的线程安全问题。

  • 确定是否是多线程环境:多线程环境下操作共享变量需要考虑线程的安全性;
  • 确定是否有增删改操作:多线程环境下,如果对共享数据有增加,删除或者修改的操作,需要谨慎。为了保证线程的同步性,必须对该共享数据进行加锁操作,保证多线程环境下,所有的线程能够获取到正确的数据。如生产者与消费者模型,售票模型;
  • 多线程下的读操作:如果是只读操作,对共享数据不需要进行锁操作,因为数据本身未发生增删改操作,不会影响获取数据的准确性。

7. 共享变量内存可见性问题

先来看下共享变量和内存可见性的定义。

共享变量:非线程私有的变量,共享变量存放于主内存中,所有的线程都有权限对变量进行增删改查操作。

内存可见性:由于数据是存放于内存中的,内存可见性意味着数据是公开的,所有线程都可对可见性的数据进行增删改查操作。

Java 内存模型规定,将所有的变量都存放在主内存(共享内存)中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,也就是我们所说的线程私有内存,线程读写变量时操作的是自己工作内存中的变量。

当一个线程操作共享变量时,它首先从主内存复制共享变量到自己的工作内存,然后对工作内存里的变量进行处理,处理完后将变量值更新到主内存。

总结:对于内存可见的共享变量,在对其进行操作时,一定要注意线程的安全问题,保证线程的安全以及数据的准确性,是多线程并发编程的重点。

8. 小结

多线程环境下进行并发编程,需要遵循多线程的并发原则,这是我们学习本节内容的基础所在。本节的重点在于对线程安全问题的关注以及共享变内存可见性操作问题的考虑。

本节内容为我们后续进行的多线程并发的内容奠定了良好的理论基础。