1. 进程 | 线程、并行 | 并发
- 软件(Software):一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件之分。
- 程序(Program):为完成特定任务、用某种语言编写的一组指令的集合,指一段静态的代码,是静态对象。
- 进程(Process):程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。进程是操作系统资源分配的基本单位。
- 线程(Thread):一个程序内部的一条执行路径。线程是处理器任务调度和执行的基本单位(最小单元)。
- 并行(Parallel):多个CPU同时执行多个任务。两个或多个事件在同一时刻发生。
- 并发(Concurrent):单个CPU(采用时间片)同时执行多个任务。两个或多个事件在同一时间段内发生。
Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。
每个线程拥有独立的运行栈和程序计数器,线程切换的开销小。一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患(线程安全问题)。在JVM中,Java堆和方法区的区域是多个线程共享的数据区域,多个线程可以操作保存在堆或者方法区中的同一个数据,保存在堆和方法区中的变量就是Java中的共享变量。
- 类变量:存放在JVM的方法区中
- 成员变量:存放在JVM的堆内存中
- 局部变量:存放在JVM的栈内存中
进程和线程的区别
- 进程是一个独立的运行环境,而线程是在进程中执行的一个任务。他们两个本质的区别是是否单独占有内存地址空间及其它系统资源(比如I/O)。
- 系统在运行时会为每个进程分配不同的内存区域,进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
- 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
- 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。
- 进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位 。
多进程的方式也可以实现并发,为什么我们要使用多线程?
- 进程间的通信比较复杂,而线程间的通信比较简单,通常情况下,我们需要使用共享资源,这些资源在线程间的通信比较容易。
- 进程是重量级的,而线程是轻量级的,故多线程方式的系统开销更小。
进程调度算法:
- 先到先服务
- 短作业优先
- 时间片轮转
- 优先级调度
- 多级反馈队列(既能使高优先级的作业得到响应,又能使短作业进程迅速完成。)
Java的线程调度方法:
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略;
- 高优先级的线程的调度策略采用抢占式,优先级高的线程比优先级低的线程会有更大的几率优先执行。
对于单CPU的计算机来说,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令;所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。线程的运行状态中包含两种子状态,即就绪(READY)和运行中(RUNNING),而一个线程想要从就绪状态变成运行中状态,这个过程需要系统调度,即给线程分配CPU的使用权,获得CPU使用权的线程才会从就绪状态变成运行状态。给多个线程按照特定的机制分配CPU的使用权的过程就叫做线程调度。
守护线程 VS 用户线程
- 它们几乎在每个方面都是相同的,唯一的区别是判断 JVM 何时离开。
- 守护线程是用来服务用户线程的,通过在 start() 方法前调用 thread.setDaemon(true) 可以把一个用户线程变成一个守护线程。
- 在 Daemon 线程中产生的新线程也是 Daemon 线程。
- Java 垃圾回收就是一个典型的守护线程。
- 若 JVM 中都是守护线程,当前 JVM 将退出。JVM 退出时守护线程中的 finally 块并不一定会执行。
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,因此感觉不出来。如果是多核CPU的话,才能更好的发挥多线程的效率。以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率。
- 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
何时需要多线程?
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如:用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
- 一个 Java 应用程序 java.exe,至少有三个线程:main() 主线程、gc() 垃圾回收线程、异常处理线程。
- 使用多线程的原因:更多的处理器核心、更快的响应时间、更好的编程模型。
package com.thread.java;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
/**
* @author rrqstart
* @Description 一个普通的Java程序包含的线程
*/
public class MultiThread {
public static void main(String[] args) {
//获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
//不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
//遍历线程信息,仅打印线程ID和线程名称
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo.getThreadId() + " : " + threadInfo.getThreadName());
}
}
}
//程序执行结果:
6 : Monitor Ctrl-Break //监控Ctrl-Break中断信号
5 : Attach Listener //该线程用于JVM进程间的通信,但是它不一定会启动
4 : Signal Dispatcher //分发处理发送给JVM信号的线程
3 : Finalizer //调用对象的finalize方法的线程
2 : Reference Handler //清除Reference的线程
1 : main //main线程,用户程序的入口
2. 线程的创建和使用
Java 语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来体现。
package java.lang; //since JDK1.0
public class Thread implements Runnable {
private Runnable target;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(