进程:
- 该程序由指令和数据组成。如果指令需要执行,数据需要读写,则需要将指令加载到cpu,数据加载到内存。该过程用于加载指令和管理IO、管理内存的
- 当一个程序从磁盘加载代码到内存执行时,就会打开一个过程
- 程序可视为程序的例子,大多数程序可以同时操作多个例子。
线程:
- 一个过程有一个或多个线程
- 线程是指令流,将指令流中的指令交给cpu执行
- 作为资源分配的最小单位,线程是java调度的最小单位。
两者对比:
- 过程基本上是独立的,线程存在于过程中,是过程的子集
- 该过程拥有内存空间等共享资源,供内部线程共享
- 过程之间的通信比线程更复杂
- 同一台计算机不同过程之间的通信称为IPC
- HTTPP等不同计算机之间的通信需要遵守相同的网络协议
- 由于共享过程中的内存,线程通信相对简单
- 线程较轻,线程上下文切换成本低于过程上下文切换
并发:
单核cpu同时执行多个线程
并行:
多核cpu同时执行多个线程
2.3 应用程序异步调用(案例1)从调用的角度来看,如果
- 不需要等待结果返回,执行异步
同步等待
/** * 同步等待 * @author xc * @date 2023/4/30 15:28 */@Slf4jpublic class Sync2_3 { public static void main(String[] args) { FileReader.read("D:学习\随便玩\wxPush\\DEPLOY.md"); log.debug("do other things ..."); }}
你可以看到,在执行其他事情之前,你需要文件才能完成
- 在执行同步之前,需要等待结果返回
不要等异步
/** * 异步不等待 * @author xc * @date 2023/4/30 15:35 */@Slf4jpublic class Async2_3 { public static void main(String[] args) { new Thread(()-> FileReader.read("D:学习\随便玩\wxPush\\DEPLOY.md")).start(); log.debug("do other things ..."); }}
你可以看到,你可以做其他事情而不等待文件读完。
1)设计
多线程可以使方法变为异步。例如,如果读取磁盘文件需要30分钟,如果没有线程调度机制,CPU在这30分钟内什么都做不了,其他代码将被暂停
2)结论
- 例如,在项目中,视频文件需要更多的时间来转换格式,这是为了打开一个新的线程来处理视频转换,以避免阻塞主线程
- tomcat的异步servlet也是类似的目的,使用户的线程处理耗时,避免阻塞tomcat工作线程
- 在ui程序中,打开新的线程进行其他操作,以避免阻塞ui线程
充分利用多核cpu的优势,提高运行效率
计算1花费 10 计算ms2的费用 11 计算ms3的费用 9 ms汇总费用 1 ms
- 如果时间串行需要31,需要31 ms
- 如果时间平行,需要3次计算中最长的时间+1ms,即12ms
设计代码
package com.itcast;import org.openjdk.jmh.annotations.*;import java.util.Arrays;import java.util.concurrent.FutureTask;@Fork(1)@BenchmarkMode(Mode.AverageTime)@Warmup(iterations=3)@Measurement(iterations=5)public class MyBenchmark { static int[] ARRAY = new int[1000_000_00]; static { Arrays.fill(ARRAY, 1); } @Benchmark public int c() throws Exception { int[] array = ARRAY; FutureTask<Integer> t1 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[0+i]; } return sum; }); FutureTask<Integer> t2 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[250_000_00+i]; } return sum; }); FutureTask<Integer> t3 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[500_000_00+i]; } return sum; }); FutureTask<Integer> t4 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[750_000_00+i]; } return sum; }); new Thread(t1).start(); new Thread(t2).start(); new Thread(t3).start(); new Thread(t4).start(); return t1.get() + t2.get() + t3.get()+ t4.get(); } @Benchmark public int d() throws Exception { int[] array = ARRAY; FutureTask<Integer> t1 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 1000_000_00;i++) { sum += array[0+i]; } return sum; }); new Thread(t1).start(); return t1.get(); }}
可以看出,与串行性能相比,并行性能有了很大的提高
总结
- 在单核cpu下,多线程不能实际提高程序的运行效率,只是为了在不同的任务之间切换,不同的线程轮流使用cpu,以免一个线程总是占用cpu,其他线程不能工作
- 多核cpu可以并行运行多个线程,但能否提高程序运行效率取决于情况
- 有些任务,经过精心设计,并行分割和执行,当然可以提高程序的运行效率。但并非所有的计算任务都可以分割
- 并非所有任务都需要拆分。如果角色的目的不同,谈论拆分和效率是没有意义的
- IO操作不占用CPU,但我们复制使用阻塞IO,相当于不使用CPU需要等待IO结束,未能充分利用线程,然后有非阻塞IO和异步IO