线程和进程概念

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeG1oLXN4aC0xMzE0,size_20,color_FFFFFF,t_70,g_se,x_16一、什么是线程和进程?

 

 

进程:

 

是程序的一次执行过程,是系统运行程序的基本单元(就比如打开某个应用,就是开启了一个进程),因此进程是动态的。系统运行一个程序即是一个程序从创建、运行到消亡的过程。

 

在 Java 中,当我们启动 main 函数时其实就是启动了 JVM 进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

 

线程:

 

线程与就进程相似,但线程是一个比进程更小的执行单位。一个进程在执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个进程,或是在各个进程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

 

 

 

二、线程与进程的关系,区别及优缺点?

 

从 JVM 角度说进程和线程之间的关系

 

下图是 Java 内存区域,通过下图我们从 JVM 的角度说明线程与进程之间的关系。

 

 

 

可以看出,一个进程可以有多个线程,多个线程共享进程的堆和方法区(JDK 1.8 之后的元空间)资源。但是每个线程有自己的程序计数器、虚拟机栈和本地方法栈。

 

综上:线程是进程划分成的更小的运行单位。线程与进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程则相反。

 

为什么程序计数器、虚拟机栈和本地方法栈是线程私有的呢?为什么堆和方法区是线程共享的呢?

 

(1) 程序计数器为什么是私有的?

 

首先明确程序计数器的作用:

 

字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制。如:顺序执行、选择、循环、异常处理。

在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程运行到哪了。

需要注意的是:如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

 

所以,程序计数器私有主要是为了线程切换后能够恢复到正确的执行位置。

 

(2) 虚拟机栈和本地方法栈为什么是私有的?

 

虚拟机栈:每个Java 方法在执行的同时会创建一个帧栈用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至完成的过程,就对应一个帧栈在 Java 虚拟机中入栈和出栈的过程。

本地方法栈:和虚拟机的作用非常相似。区别是:虚拟机为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 native 方法服务。在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

 所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

 

(3) 堆和方法区

 

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用来存放新创建的对象(所有的对象都在这里分配内存);方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。

 

 参考:JavaGuide 公众号及其相应的 Github

 

 

 

 三、并发和并行有什么区别?

 

并发:同一时间段,多个任务都在执行(单位时间内不一定同时执行);

并行:单位时间内,多个任务同时执行。

并发的关键是你有处理多个任务的能力,不一定要同时。 而并行的关键是你有同时处理多个任务的能力。  

 

 

 

四、为什么要使用多线程?

 

先总体上:

 

从计算机底层来说:线程可以比作是轻量级的进程,是程序执行的最小单元,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。

从当代互联网发展趋势来说:现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正式开发高并发系统的基础,利用好多线程机制可以大大提高系统的并发能力以及性能。

再深入到计算机底层:

 

单核时代:在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。

多核时代:多核时代主要是为了提高 CPU 的利用率。

 

 

五、使用多线程可能会带来什么问题?

 

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁等,还有受限于硬件和软件和资源闲置问题。

 

 

 

六、说说线程的生命周期和状态。

 

Java 线程在运行的生命周期中的指定时刻只可能指定处于下面几种不同状态的其中一个状态:

 

新建状态(NEW):新创建了一个线程对象;

就绪状态(RUNNABLE):线程创建后,其他线程调用了该对象的 start() 方法。该方法状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权;

运行状态(RUNNING):就绪状态的线程获取了 CPU,执行程序代码;

阻塞状态(BLOCKED):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。知道线程进入就绪状态,才有机会转到运行状态。阻塞的情况分为三种:

等待阻塞:运行的线程执行 wait() 方法,JVM 会把该线程放入线程池中。

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。

其他阻塞:运行的线程执行 sleep() 或 join() 方法,或者发出了 I/O 请求时,JVM 会把该线程设置为阻塞状态。当 sleep() 超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

死亡状态(DEAD):线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期。

线程在生命周期中并不是固定处于一个状态,而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图(图为《Java 并发编程的艺术》)

 

 

 

可以看出:线程创建之初处于 NEW (新建) 状态。调用 start() 方法后开始运行,线程这时候处于 READY (可运行) 状态。可运行状态的线程获得了 CPU 时间片 (timeslice) 后就处于 RUNNING (运行)状态。线程执行了 wait() 方法后,线程进入 WAITING (超时等待) 状态相当于等待状态的基础上增加了超时限制,比如 sleep(long millis) 方法或 waiting(long millis) 方法可以将 Java 线程置于 TIME WAITING 状态。当超时时间达到后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED (阻塞)状态。线程在执行 Runnable 的 run() 方法之后将进入到 TERMINATED (终止) 状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值