进程、线程、多线程
操作系统、进程、线程之间的关系
操作系统是包含多个进程的容器,而每个进程又都是容纳多个线程的容器。
- 进程:使用 fork(2) 系统调用创建的 UNIX 环境(比如:文件描述符,用户 ID 等),它被设置为运行程序。 进程就是代码的实例化对象。是资源分配的基本单位。
- 线程:在进程上下文中执行的一系列指令。
Windows 下查看进程下所有线程
* 然后将解压后的 pslist.exe 复制到 C:\Windows\System32 文件夹下
* 执行 PSlist -dmx PID (用来查看指定进程下有哪些线程)
* windows10 可以启动 资源监视器 来查看 java.exe进程的线程数
模拟线程
/**
* 创建 100 个线程,用任务管理器的CPU栏目看Java线程数量的变化,10秒钟后线消失。
*/
public class Create100Threads {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
进程和线程的不同
- 起源不同:处理器速度比外设要快(键盘、硬盘),为了提高 CPU 利用率才诞生了线程,以提高程序的执行效率。
- 概念不同:进程是具有独立功能的程序运行单元,是系统分配资源和调度基本单位。线程是 CPU 的调度单位。
- 内存共享方式:不同进程之间的内存通常情况下是不共享的(除非使用 IPC 进程间通讯), 线程与线程之间内存和通信相比进程要容易的多,因为它们本身服务于同一个进程。
- 拥有资源不同
# 线程共享的内容
* 进程代码段
* 进程的公有数据(利用这些共享的数据,线程很容易的实现相互的通讯)
* 进程打开的文件描述符
* 信号处理器
* 进程的当前目录
* 进程用户 ID 与 进程组 ID
# 线程独有的内容
* 线程ID
* 寄存器组的值
* 线程的堆栈
* 错误返回码
* 线程的信号屏蔽码
- 数量不同:1 个进程包含多个线程,1 个线程至少拥有一个线程
- 开销不同
* 线程的创建、终止时间比进程短
* 同一进程内的线程切换时间比进程切换短
* 同一进程的各个线程间共享内存和文件资源,可以不通过内核进行通信
进程和线程的相似点
- 生命周期:状态上很相似。
Java 语言和多线程的渊源
- Java 设计之初支持多线程
- Java 语言多用于服务端(服务端经常需要用到多线程)
- 一对一映射到操作系统的内核线程
JVM 自动启动线程
- Signal Dispatcher:把操作系统发来的信号分发给适当的处理程序。(连接操作系统和应用程序)。
- Finalizer:每个对象结束的时候会执行对象的 finalize() 方法。虽然 finalize() 已经不再推荐使用。
- Reference Handler:和 GC、引用相关的线程。
- main:主线程,用户程序的入口。
多线程
多线程程序:如果一个程序允许运行两个或以上的线程,那么它就是多线程程序。
多线程:多线程是指在单个进程中运行多个线程。
为什么需要多线程?
- 最主要的目的就是提高 CPU 利用率:多线程程序可以充分利用多核 CPU 资源。(除 CPU 之外的其他外设速度太慢)
- 避免无效等待(IO 的时候可以做别的事情)
- 提高用户体验:避免卡顿、缩短等待时机
- 便于编程建模(将一个复杂的任务分解成若干的子任务)
- 摩尔定律失效 (纳米级别到达了极限)
- 阿姆达尔定律:处理器越多,程序执行就越快,但有上限,取决于程序中串行部分的比例和并行的比例,程序的并行比例越高,多处理器的效果越明显。(如果程序本身的代码全部都是串行的,那么多线程并不能提高运行速度)
* tomcat 用多个线程接收进来的HTTP请求,而不是排队等待单一的线程处理
* Android 主线程的重要任务之一就绘制屏幕节界面,该主线程中不允许进行IO/操作或网络请求,目的就是为了避免卡顿,影响用户的交互
什么场景中会用到多线程
- 为了同时做多件不同的事(打开网页同时听音乐、后台定时任务)
- 为了提高工作效率、处理能(Tomcat、并行下载、NIO)
- 需要同时有很大并发量的时候(压测模拟请求)
多线程的局限性
- 性能问题:上下文切换带来的消耗。
- 异构化任务(任务结构不一样)很难高效并行
- 带来线程安全问题:包括数据安全问题(例如 i++ 总数的不一致)以及线程带来的活跃性问题(线程饥饿、死锁等…)
串行、并行(Parallelism)、并发(Concurrent)
- 串行:排队
- 并发(Concurrent):单处理器的逻辑上同时执行。在同一时刻只能运行一个线程,多个线程交替运行在同一。
- 并行(Parallelism):多处理器的物理上的同时执行。在同一时间多个处理器同时执行多个任务,并发不一定是并行(如果是单处理器)。
并发的 2 种概念
- 形容多个任务的执行状态(多个任务看上去表面上可以同时执行):并行一定是并发。(并发可以通过添加处理器数量升级为并行)。
- 对 “并发性” 的简称
# 具有并发性的程序
* 不同的部分可以无序或同时执行,且不影响最终的执行结果
* 在不同核心数的计算机上的不同表现
* 并行和并发的概念并不在同一纬度
* 程序具有并发性是并发执行或并行执行的前提条件和必要条件。
单核 CPU 运行多线程的意义?
- 单核 CPU 可以做到逻辑上的并行,虽然无法做到物理意义上的并行。
- 程序并不知道未来运行的计算机是单核还是多核,所以在编写的时候肯定是尽量扩大代码的并行比例,以此来最多程度利用硬件资源。
- 即便是运行在单核 CPU 上,多线程中其中一个线程执行缓慢或被阻塞(等待 I/O),其他的线程就可以充分利用阻塞的时间去做其他的事情,让程序可以高效的执行。
是什么让并发和并行成为可能?
- CPU 升级(摩尔定律还没失效之前)
- 操作系统升级(划分时间片)
- 编程语言的升级
高并发只是并发的升级版吗?
什么是高并发?
同时有很多请求发送给服务器系统,服务器就会并行处理。
* 双11:天猫每秒钟订单的创建峰值高达 49.1 万笔。
* 微信摇一摇:春晚 72亿次,峰值每分钟 8.1 亿次,同时送出了2.1亿个红包。
* 12306的PV值最高297亿次,售出1000万张票。
高并发和多线程的联系和不同?
- 高并发指的是大量请求同时到达服务器的状况。
- 多线程是一种解决方案。解决高并发带来的线程安全问题、性能问题。
- 多线程是应对高并发的一种重要的解决方案,这种解决方案如果处理不当会衍生出更多的问题。(金融系统、订单系统 后果很严重)
- 面对高并发并不一定要通过多线程这种方法去解决。(用 reids 去解决 MySQL 的高并发)
- 高并发并不意味着是多线程:Redis(底层用单线程处理,吞吐量也很大、并发也很高)
- 高并发的解决方案有很多种。
* 线程安全问题(HashMap ==> ConcurrentHashMap)
* 提高硬件的利用率 (tomcat 内部采用多线程模型 来响应 多个请求)
高并发有哪些指标?
- QPS(Queries Per Second):每秒查询数,每秒请求数。
- 带宽:网络带宽。
- PV(Page View):24 小时页面访问量。
- UV(Unique Visitor):24 小时页面的用户访问数量。(一个用户访问 10 次网站,PV + 10、UV +1)
- IP 和 UV 的区别
* 如果是同一个cookie,IP变了,IP +1 , UV 不变。
* 同一个IP中会有多个不同的 cookie,IP 不变,UV + N。
- 并发连接数(The number of concurrent connections):某个时刻服务器所接受的请求个数。通常会大于在线用户数量,因为一个用户可以通过发起多个不同请求。
- 服务器平均请求等待时间(Time per request: across all concurrent request):服务器处理一个请求所需要消耗的时间。
同步与异步、阻塞与非阻塞
- 同步与异步:被调用者是否能立刻返回。(服务器的行为)
- 阻塞与非阻塞:调用者在服务器未返回之前是否能做别的事。(发送方的行为)
* 同步阻塞:盯着水壶烧水,水壶沸腾了也不会发出声音。
* 同步非阻塞:每个几分钟看一下烧水,水壶沸腾了也不会发出声音。
* 异步阻塞:盯着水壶烧水,直到水壶沸腾了会发出声音。
* 异步非阻塞:可以做别的事情,水壶沸腾了会发出声音。
线程分类
守护线程的作用:jvm 垃圾清理线程。
尽量少使用守护线程,因其不可控性(譬如:执行任务到一半,所有用户线程都运行结束了,这时守护线程就会随着 JVM 一起退出。),如果一定要使用守护线程,也一定不要在守护线程里去进行读写操作、执行计算逻辑。(因为守护线程对于用于来说是不可控的)
- 用户线程:只要用户线程没有执行完毕,main 程序就无法退出。
- 守护线程:任何一个守护线程都是整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。当 JVM 实例中最后一个用户线程结束时,也随 JVM 一起退出。
/**
* 守护线程Demo
*/
public class DaemonThreadDemo implements Runnable{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new DaemonThreadDemo());
thread.setDaemon(true); //一定要在start()方法之前设置为守护线程
thread.start();
Thread.sleep(2000L);
}
}