进程
程序由指令和数据组成,数据要读写,就必须将指令加载至CPU,数据加载至内存。进程要做的就是加载指令、管理IO的。 当一个程序被运行,从磁盘加载这个程序的代。码至内存,就开启了一个进程。进程是加载指令,管理内存,管理IO的。 进程之间相互独立。
线程
一个进程可以分为多个线程。一个线程就是一个指令流,将指令流以一定的顺序一条条交给cpu去执行。指令的运行会涉及到 磁盘 、 网络等设备。
线程是最小的调度单位,进程是资源分配的最小单位 , 在windows 中进程只是作为线程的容器,是不活动的
并行
在多核cpu 下,比如说两个线程,每个核都可以调度运行线程。这时候线程可以说是并行的。
并发 concurrent
时间片轮转,在单核cpu下,线程在围观表现上实际上还是串行化的,但是 时间片切换非常快,感觉是并行的。
线程轮流使用cpu
golang作者这样说
- 并行是同一时间动手做多件事情的能力
- 并发是同一时间应对多件事情的能力
生活例子
- 并发 :家庭主妇做饭、喂奶、打扫卫生,一个人轮流交替多件事情
- 并发且并行 : 雇了一个保姆,一起做以上三件事,既有并发、也有并行。一个人必会轮流做,也会产生共享资源的竞争,比如说一个人使用锅、另一个人只能等着,不能使用这口锅。
- 并行:雇了2个保姆,3个人做三件事互不干扰,并行。
同步 和 异步
方法调用方面来讲:需要等待结果返回,才能继续运行,这就是同步
方法不需要等待结果返回。就能继续运行,这就是异步
使用多线程就可以做到,异步调用。
效率方面
单核下,多线程并不能实际提高运行效率,只是为了能够在不同任务之间进行切换,不同线程轮流使用cpu,不至于一个线程霸占cpu,其他线程没法干活。
多核下,任务的目的如果相同的话,将任务拆分,并行执行,可以提高程序的运行效率。
I/O 操作不占cpu、但是非常耗时,一般拷贝文件使用的是阻塞IO,这种线程虽然不用CPU,但需要一直等待IO结束,不能充分利用线程。 出现技术阻塞IO和异步IO
线程创建和运行
- Thread
- Runnable(可运行任务) 和 Thread(线程)
使用 Runable 更容易与线程池等高级API进行配合,Runable 让任务脱离了Thread 继承体系,更加灵活。
- FutureTask:实现了 RunableFuture接口,RunableFuture 又实现了 Runable 接口和 Future 接口。 get方法 同步的等待。
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask 构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
Callable 可以 抛出异常,且有返回值。
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
配合 Thread 使用 因为继承关系,实现了 Runable 接口,可以构造一个线程。
线程运行原理
zhuanlan.zhihu.com/p/574278435
每个线程都有自己独立的虚拟机栈(线程私有),在执行一个方法时,会同步在虚拟机栈中建立一个栈帧,方法运行完成后栈帧弹出,执行栈帧返回地址的下一行代码,每个线程只有一个活动栈帧,线程之间的栈帧互不干扰。
线程上下文切换 (频繁发生影响性能)
当一个线程的时间片用完后或者其他自身原因被迫暂停运行了,这时候,另外一个线程或者、进程或者其他进程的线程就会被操作系统选中,用来占用处理器。这种一个线程被暂停,一个线程包选中开始执行的过程就叫做上下文切换。 原因:
- 非自发性
- cpu 时间片用完
- 垃圾回收 stop the world
- 更高优先级的线程要运行
- 自发性
- 调用 sleep 、 wait 、 join、park、lock 等方法。
- synchronized 关键字。
当上下文切换发生时 ,需要由操作系统 保存当前线程的状态(每个栈帧得信息),并恢复另一个线程的状态,Java中对应得是程序计数器得工作(记住下一条指令得地址)。
所以 线程数超过 cpu 核心数就会引发线程上下文切换,影响 程序性能。