定义
1、进程:进程是一个具有独立功能的程序关于某个数据集合的以此运行活动。 是系统进行资源分配和调度的独立单位,也是基本的执行单元。是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动。
进程结构特征: 由程序、数据和进程控制块三部分组成。具有 独立性、并发性、异步性和动态性的特点。
(1)、进程的概念主要有两点:
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括
文本区域(text region)--存储处理器执行的代码,
数据区域(data region)--存储程序执行期间的一些数据变量,
堆栈(stack region)--存储动态分配的内存和本地变量及指令。
第二:进程是一个“执行中的程序”。程序是一个没有生命的实体,只有在运行时处理器才会赋予它生命,才能成为一个活动的实体,我们称其为“进程”。
每一个进程都会有一个独一无二的编号,被称为进程标识码,简称PID(Process,identifier),它是一个取值为1-32768.但是init是一个特殊的进程。所谓的init进程,是一个内核启动的用户级进程,也是系统上运行的所有其他进程的父进程,他会观察子进程,并在需要的时候启动,停止,重新启动它们,init进程主要完成系统各项的配置。linux系统中,init从根文件夹系统目录里的/etc/inittab文件里获取信息。是所有进程的发起者和控制者,内核启动后,便由init进程来进行各项配置。
(2)、进程的三种状态:
就绪(Ready)状态:当进程分配到除CPU以外的必要资源后,只要再获得CPU,便可以立即执行,进程这时的状态为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。
阻塞(Blocked)状态:正在执行的进程由于发生某事件或接受某消息无法继续执行时,便放弃处理机而处于暂停状态,也即进程的执行收到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态和封锁状态。通常使进程处于阻塞的原因有:请求I/O,申请缓冲空间。也会产生一个相应的阻塞队列。
运行(Running)状态:进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态,在多处理机系统中,则有多个进程处于执行状态;
其关系如下图所示:
2、线程:线程是进程中的执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源( 程序计数器,一组寄存器和栈),但它可与同属一个进程的其他线程 共享进程所拥有的全部资源。
线程优点:
- (1)易于调度。
- (2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一个程序的不同部分。
- (3)开销少。创建线程比创建进程要快,所需开销少,占用的资源也少;
- (4)充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分的运行。
二、进程与线程的区别
(1)调度:线程作为处理器调度和分配的基本单位,而进程是作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,有自己独立的地址空间;线程不拥有系统资源,但可以访问隶属于进程的资源,共享进程的地址空间.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
三、进程和线程的关系
(1)二者均可并发执行.
(2)线程是指进程内的一个执行单元,也是进程内的可调度实体。一个程序至少有一个进程,一个进程至少有一个线程,一个线程只属于一个进程.
(3)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(4)处理机分给线程,即真正在处理机上运行的是线程。
(5)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间, 一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
四、进程间通信的方式
(1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
(2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
(3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限的进程则可以从消息队列中读取信息。
(4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
(5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。
(6)套接字(socket):这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
多线程的优缺点
优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
多线程使用场景:
场景1:当某个接口响应速度很慢的时候,可以使用多线程提升响应速度。前提是这个接口获取信息的逻辑互相独立,比如首页接口,需要获取列表A,列表B,列表C等,而列表ABC三者之间互相独立(也就是不需要获取到A,就能获取到B),互相之间没有关系。这种情况就可以使用多线程去优化,总耗时为获取3个列表当中耗时最长的那个。
场景2:多线程分批次处理集合数据,不处理重复的数据。比如有一张学生表,数据量很大,你需要根据表当中的姓名和身份证号这两个字段去调用外部接口(外部接口一次只能校验一条数据),校验之后还需要更新这张表。这个时候可以使用多线程分批次处理,大大提高效率。
public class TestApp {
//配置需要同时开启多少个线程进行处理
private final int threadNum = 10;
/**
* @param list 要处理的集合数据,如果查询较慢,可以多线程分批次查询然后汇总在一块
*/
public void handleData(List<User> list) {
int size = list.size();
if (size == 0 || list == null) {
return;
}
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
List<Future<List<User>>> futures = new ArrayList<Future<List<User>>>(size);
for (int i = 0; i < threadNum; i++) {
//将数据分成threadNum份,线程同时执行
int from = size / threadNum * i;
int to = size / threadNum * (i + 1);
if (i == threadNum - 1) {
to = size;
}
List<User> subList = list.subList(from, to);
Callable<List<User>> task = new Callable<List<User>>() {
@Override
public List<User> call() {
List<User> list1 = new ArrayList<>();
//对每个线程(线程中的每份数据)的逻辑操作
for (User user : subList) {
//调用外部接口
Boolean res = checkNameAndIdCard(user.getName, user.getIdCard);
user.setRes(res);
list1.add(user);
}
return list1;
}
};
//添加线程到队列
futures.add(executorService.submit(task));
}
//多线程处理后的集合汇总到all
List<User> all = new ArrayList<>();
for (int i = 0; i < futures.size(); i++) {
try {
List<User> list1 = futures.get(i).get();
all.addAll(list1);
} catch (Exception e) {
e.printStackTrace();
}
}
//批量更新数据库
executorService.shutdown();
}
/**
* 模拟外部接口
*/
public Boolean checkNameAndIdCard(String name, String idCard) {
return true;
}
}
@Data
class User {
private Integer id;
private String name;
private String idCard;
private String res;
}
整体流程:
先把要处理的集合从数据库全部捞出来
根据要开的线程数量对集合进行均分
开多线程对每个集合进行处理,处理后返回结果
Future.get()会造成主线程阻塞,也就是当所有future都得到结果后主线程才能继续执行下去
场景3:做异步。比如页面上需要导出excel文件,由于业务要求,需要导出全部数据,导出全部数据大概需要10分钟左右,那如果使用同步的方式,用户需要在这个导出页面等待10分钟,不能做其它操作,这样肯定是不行的。那么可以采用异步,用户点击导出,导出接口主线程往数据库当中插入一条导出记录,开个子线程获取数据,写入Excel文件,完成之后更新导出记录,当中,然后主线程直接给用户返回。这样用户点击导出会生成一个导出记录信息,不用在这里等待,等导出完成,之后可以在导出记录当中进行下载。代码就不贴了。
场景4:监听线程,如果某个线程执行时间过长(可能死循环了),超过了自定义的时间,就把该线程干掉,释放cpu资源
情况1:开一个线程去执行这个任务
public class MainSingle {
public static final ExecutorService pool = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws Exception {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "进来了" + this.hashCode());
try {
Thread.sleep(7000);
} catch (InterruptedException e) {}
}
};
FutureTask futureTask = new FutureTask<>(runnable,null);
pool.submit(futureTask);
long start = System.currentTimeMillis();
// 判断是否完成,如果没完成就是一直循环判断
while (!futureTask.isDone()) {
long now = System.currentTimeMillis();
if ((now - start) >= 6000) {//超时时间3000毫秒
futureTask.cancel(true);
System.out.println("线程超时了,终止");
}
}
pool.shutdown();
}
}
情况2:开多个线程,哪个线程死循环,就关闭哪一个线程,不影响其它线程
知识来源:
【23版面试突击】进程和线程的区别,使用线程真的能节省时间?_哔哩哔哩_bilibili
操作系统:进程与线程之间的区别及联系 - 知乎
多线程使用场景_多线程的应用场景_木偶亽~的博客-CSDN博客