文章目录
一、进程与线程
最开始是没有线程这个概念的 , 一个应用程序就是一个进程 , 应用程序运行时 , 如果还要处理与用户交互的逻辑 , 二者只能 交替进行 , 这样 CPU 执行效率就很低 ;
CPU 是整个计算机系统中的 稀缺资源 , 程序的运行 , 计算 都需要依赖 CPU 完成 ;
为了 高效利用 CPU 这个稀缺资源 , 引入了线程概念 ;
进程 : 每个应用都是一个独立进程 , 是 资源分配 , 调度 的最小单元 ;
线程 : CPU 调度的最小单元 ;
二、并发
CPU 是多核的 ;
进程 是在 物理内存 中执行的 ( 内存条 或 RAM ) ;
每个进程 中 有若干 线程 ;
CPU 运行线程时 , 通过 OS 线程调度 , 在 某个 CPU 的 某个 核 上 执行 某个 进程 的 某个 线程 ;
程序的执行 , 最终是靠指令进行执行 ;
进程 在 内存中 , 会被划分一块 独立的区域 , 每个进程之间的内存都是 隔离 的 , 一个进程的崩溃 , 不会影响其它进程 ;
每个线程执行时 , JVM 都会为该线程单独分配 线程栈 , 本地方法栈 , 程序计数器 , 三者都是线程独有的数据 ;
程序运行 的 指令 , 就放在 上面的 线程栈 中 ;
每个 线程栈 中都有 一串指令 , 等待执行 ;
这些线程栈 , 不能 串行 执行 , 必须 并发 执行 , 才能保证所有的应用程序 , 都能得到很好的用户体验 ;
并行 是 同一个 时间点 处理多个事件 ;
并发 是 同一个 时间段 处理多个事件 ;
三、线程间通信
线程间通信 :
假设有 2 2 2 个线程 A 和 B ;
线程一旦执行后 , 会在内存中分配 线程栈 , 该线程栈中有一块 本地内存 ;
有一些对象是共享的 , 所有线程都可以访问 , 如 堆内存 , 在 线程栈 中的 本地内存 中 , 有一个 共享变量的副本 ;
在 主内存 中 , 有很多 共享变量 ;
主内存中有变量 int a = 1
, 如果线程 A 中想要访问变量 a
, 就会将该变量 int a = 1
变量 复制到 线程 A 的 本地内存中 ;
如果线程 B 也想访问 , 则将该变量 int a = 1
变量 复制到 线程 B 的 本地内存中 ;
如果在 线程 A 中 , 对变量 a
进行 +1
操作 , 只是对 线程 A 本地内存 中的 变量 a
副本进行了 +1
操作 , 主内存 和 线程 B 中的 a
变量 没有变化 ;
假如 线程 A 和 线程 B 同时对 本地内存 中的变量 a
进行操作 , 那么就有可能出现 a
取值异常的情况 ;
主内存 中的数据 , 对所有的线程都可见 ; 但是 线程 A 和 线程 B 之间 , 互相不知道对方线程 本地内存 中的数据 ;
这种情况就是线程不安全的情况 ;
四、Java 并发 3 特性
Java 并发的 3 3 3 特性 :
-
原子性 : 每个操作都是 不可拆分的原子操作 ; 在线程中进行
a++
就不是原子操作 , 该操作分为 3 3 3 个步骤 , 首先从主内存中读取a
变量 , 然后进行自增操作 , 最后在将自增后的值写回主内存中 ; - 可见性 : 多个线程 访问同一个变量 , 该变量一旦被 某个线程修改 , 这些线程必须可以 立刻看到被修改的值 ;
- 有序性 : 程序按照 代码先后顺序 执行 ;