Java 并发编程-基础知识

基础概念

并发与并行

在单核 CPU 时间,同一时间只能有一个任务占用 CPU 运行。并发指的就是,在一段时间内,有多个任务看似在“同时”运行。实际上是操作系统在很短的时间内切换不同任务运行造成的错觉。

随着硬件的发展,一个 CPU 可以有多个核。这也意味着在同一时刻,真正有多个任务在同时占用着 CPU 的计算资源在运行。也称为并行。

进程与线程

线程 是操作系统能够进行运算调度的最小单位。

进程 是指计算机中已运行的程序。一般来说是操作系统分配资源的基本单位。进程内部有多个线程,线程可能共享某部分的资源。

同步与异步

这两个概念一般用来描述方法的调用。

同步的方法调用,调用者必须等待调用方法返回,才会继续后面的行为。

异步的方法调用,调用者调用方法,方法会立即返回,调用者就不用停下来等待,而继续去忙自己的事情。

多线程的缺点

并发编程的目的是为了提高程序的性能,但并不是创建更多线程就能让程序更快的运行。
上下文切换带来的消耗
线程的切换,需要保存当前线程运行的状态,以便下一次加载继续运行。这是有开销的
多线程带来的资源消耗
线程的粒度虽小,但是仍然需要占用一定资源。系统也需要分配资源来维护这么多的线程。

Java 多线程的相关知识

内存模型

先引用下图表示一下 Java 内存模型和硬件系统架构的“交流”情况。
在这里插入图片描述

硬件系统架构

上图右边所示,CPU 有寄存器,和主存之间还有多层缓存。这就带来的数据的一致性问题。

Java 内存模型

Java 在 JVM 层面,线程都有自己线程栈和内存空间,线程栈中对于变量一般有自己的一份拷贝。但是对于引用变量,虽然也是拷贝,但是变量指向的对象存储在共享的堆中。

当线程跑在 CPU 的一个核中,更新了共享对象时,但是还没来得及把结果写到主存。那对于其他已经从主存加载共享对象到 CPU 缓存或寄存器中的数据,操作的就是旧数据了,就是不安全的操作了。

打个比方,你的账户原始余额有 一个亿 ,此时你赚了一笔,线程 A 把余额更新成两个亿,但是还没有写到主存中。此时,你准备在北京买套房压压惊,线程 B 从主存读到余额的值仍为一个亿。最难过的是,当线程 A 把结果写回主存时,线程 B 毫不知情你又赚了一个亿,不认这个值,最后把剩余余额一百万写回了主存。

因为数据可以分布在不同内存区域,也就可能导致我们常说的可见性问题。通俗一点来解释可见性,就是说,一个线程对变量的操作结果是否会被其他线程及时察觉到。

例如常见的对于声明为 volatile 的变量的单个的 读/写操作发生在线程 A 中,volatile 保证这个结果会马上写回主存,而不是先放在缓冲区。

volatile 变量的读和写不会缓存到任何的缓冲区或者寄存器。

理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。— 《Java
并发编程之美》,《Java 并发编程实践》

为什么要强调单个呢,因为对 volatile 变量的复合操作是无法保证其原子性的。例如对 i++ 这个复合操作,在 CPU 层面会具体分为以下三个操作:

  1. 读取 i 的值
  2. 将 i 加 1
  3. i 写回内存

当线程 A B 同时读取 i 的值,同时加一,同时写回内存时,会发现 i 只增加了 1,而不是预期中的 A 和 B 分别加了 1,i 实际增加 2 的情况。对于这种情况就需要加锁了,保证同一时刻只能有一个线程在执行同步代码块。

如果你觉得有收获的话,点赞是对我最大的鼓励了。

发布了2 篇原创文章 · 获赞 2 · 访问量 211

猜你喜欢

转载自blog.csdn.net/FangHX25/article/details/105158092