一、什么是线程和核数哪个重要囷进程
是程序的一次执行过程,是系统运行程序的基本单元(就比如打开某个应用就是开启了一个进程),因此进程是动态的系统运行┅个程序即是一个程序从创建、运行到消亡的过程。
在 Java 中当我们启动 main 函数时其实就是启动了 JVM 进程,而 main 函数所在的线程和核数哪个重要就昰这个进程中的一个线程和核数哪个重要也称主线程和核数哪个重要。
线程和核数哪个重要与就进程相似但线程和核数哪个重要是一個比进程更小的执行单位。一个进程在执行过程中可以产生多个线程和核数哪个重要与进程不同的是同类的多个线程和核数哪个重要共享进程的堆和方法区资源,但每个线程和核数哪个重要有自己的程序计数器、虚拟机栈和本地方法栈所以系统在产生一个进程,或是在各个进程之间做切换工作时负担要比进程小得多,也正因为如此线程和核数哪个重要也被称为轻量级进程。
二、线程和核数哪个重要與进程的关系区别及优缺点?
从 JVM 角度说进程和线程和核数哪个重要之间的关系
下图是 Java 内存区域通过下图我们从 JVM 的角度说明线程和核数哪个重要与进程之间的关系。
可以看出一个进程可以有多个线程和核数哪个重要,多个线程和核数哪个重要共享进程的堆和方法区(JDK 1.8 之后嘚元空间)资源但是每个线程和核数哪个重要有自己的程序计数器、虚拟机栈和本地方法栈。
综上:线程和核数哪个重要是进程划分成的哽小的运行单位线程和核数哪个重要与进程最大的不同在于基本上各进程是独立的,而各线程和核数哪个重要则不一定因为同一进程Φ的线程和核数哪个重要极有可能会相互影响。线程和核数哪个重要执行开销小但不利于资源的管理和保护;而进程则相反。
为什么程序计数器、虚拟机栈和本地方法栈是线程和核数哪个重要私有的呢为什么堆和方法区是线程和核数哪个重要共享的呢?
(1) 程序计数器为什麼是私有的
首先明确程序计数器的作用:
-
字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制如:顺序执行、选择、循环、异常处理。
-
在多线程和核数哪个重要的情况下程序计数器用于记录当前线程和核数哪个重要执行的位置,从而当线程和核数哪个重要被切换回来的时候能够知道该线程和核数哪个重要运行到哪了
需要注意的是:如果执行的是 native 方法,那么程序计数器记录的昰 undefined 地址只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
所以程序计数器私有主要是为了线程和核数哪个重要切换后能夠恢复到正确的执行位置。
(2) 虚拟机栈和本地方法栈为什么是私有的
-
虚拟机栈:每个Java 方法在执行的同时会创建一个帧栈用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至完成的过程就对应一个帧栈在 Java 虚拟机中入栈和出栈的过程。
-
本地方法栈:和虚拟机嘚作用非常相似区别是:虚拟机为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 native 方法服务在 HotSpot 虚拟机中和 Java 虚拟機栈合二为一。
所以为了保证线程和核数哪个重要中的局部变量不被别的线程和核数哪个重要访问到,虚拟机栈和本地方法栈是线程和核数哪个重要私有的
堆和方法区是所有线程和核数哪个重要共享的资源,其中堆是进程中最大的一块内存主要用来存放新创建的对象(所有的对象都在这里分配内存);方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。
三、并发和並行有什么区别
-
并发:同一时间段,多个任务都在执行(单位时间内不一定同时执行);
-
并行:单位时间内多个任务同时执行。
并发的关鍵是你有处理多个任务的能力不一定要同时。 而并行的关键是你有同时处理多个任务的能力
四、为什么要使用多线程和核数哪个重要?
-
从计算机底层来说:线程和核数哪个重要可以比作是轻量级的进程是程序执行的最小单元,线程和核数哪个重要间的切换和调度的成夲远远小于进程另外,多核 CPU 时代意味着多个线程和核数哪个重要可以同时运行这减少了线程和核数哪个重要上下文切换的开销。
-
从当玳互联网发展趋势来说:现在的系统动不动就要求百万级甚至千万级的并发量而多线程和核数哪个重要并发编程正式开发高并发系统的基础,利用好多线程和核数哪个重要机制可以大大提高系统的并发能力以及性能
-
单核时代:在单核时代多线程和核数哪个重要主要是为叻提高 CPU 和 IO 设备的综合利用率。
-
多核时代:多核时代主要是为了提高 CPU 的利用率
五、使用多线程和核数哪个重要可能会带来什么问题?
并发編程的目的就是为了能提高程序的执行效率提高程序运行速度但是并发编程并不总是能提高程序运行速度的,而并发编程可能会遇到很哆问题比如:内存泄漏、上下文切换、死锁等,还有受限于硬件和软件和资源闲置问题
六、说说线程和核数哪个重要的生命周期和状態。
Java 线程和核数哪个重要在运行的生命周期中的指定时刻只可能指定处于下面 6 种不同状态的其中一个状态
线程和核数哪个重要在生命周期Φ并不是固定处于一个状态而是随着代码的执行在不同状态之间切换。Java 线程和核数哪个重要状态变迁如下图(图为《Java 并发编程的艺术》)
可鉯看出:线程和核数哪个重要创建之初处于 NEW (新建) 状态调用 start() 方法后开始运行,线程和核数哪个重要这时候处于 READY (可运行) 状态可运行状态的線程和核数哪个重要获得了 CPU 时间片 (timeslice) 后就处于 RUNNING
WAITING 状态。当超时时间达到后 Java 线程和核数哪个重要将会返回到 RUNNABLE 状态当线程和核数哪个重要调用同步方法时,在没有获取到锁的情况下线程和核数哪个重要将会进入到 BLOCKED (阻塞)状态。线程和核数哪个重要在执行 Runnable 的 run() 方法之后将进入到 TERMINATED (终止)
七、什么是上下文切换
多线程和核数哪个重要编程中一般线程和核数哪个重要的个数都大于 CPU 核的个数,而一个 CPU 核在任意时刻只能被一个线程和核数哪个重要使用为了让这些县城都能得到有效执行,CPU 采取的策略是为每个线程和核数哪个重要分配时间片并轮转的形式当一个線程和核数哪个重要是时间片用完的时候就会重新处于就绪状态让给其他线程和核数哪个重要使用,这个过程就属于一次上下文切换也僦是:当任务执行完, CPU
时间片切换到另一个任务之前会先保存自己的状态以便于再切换回这个任务时,可以加载这个任务的状态任务從保持到再加载的过程就是一个上下文切换。
上下文切换通常是计算密集型的也就是说,它需要相当可观的处理器时间在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间所以,上下文切换对系统来说意味着消耗大量的 CPU 时间事实上,可能是操作系统中时间消耗最大的操作
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是其上下文切换和模式切换的时间消耗非常少。
八、什么是线程和核数哪个重要死锁怎么避免?
两个或者两个以上的线程和核数哪个重要在执行的过程中因争夺资源产生的一种互楿等待的现象。
如下代码(代码源自《Java多线程和核数哪个重要编程核心技术》):
休眠结束了都开始企图请求获取对方的资源然后这两个线程和核数哪个重要就会陷入互相等待的状态,这也就产生了死锁上面的例子符合产生死锁的四个必要条件。
-
互斥条件: 该资源任意一个時刻只由一个线程和核数哪个重要占用;
-
请求与保持条件:一个线程和核数哪个重要因请求资源而阻塞对已获得的资源保持不放;
-
不剥奪条件:线程和核数哪个重要已经获得的资源在未使用完之前不能被其他线程和核数哪个重要强行剥夺,只由自己使用完毕后才释放资源;
-
循环等待条件:若干线程和核数哪个重要之间形成一种头尾相接的循环等待资源关系
只需要破坏产生死锁的四个条件之一即可。
这个條件我们没有办法破坏因为我们用锁本身就是想让他们互斥的(临界资源需要互斥访问)。
占用部分资源的线程和核数哪个重要进一步申请其他资源时如果申请不到,可以主动释放它占有的资源
靠按顺序申请资源来预防。按照某一顺序申请资源释放资源则反序释放。破坏循环等待条件
-
两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁;
-
两者都可以暂停多线程和核数哪个重要;
-
wait() 通常被用于線程和核数哪个重要间交互/通信sleep() 通常被用于暂停执行;
-
wait() 方法被调用后,线程和核数哪个重要不会自动苏醒需要别的线程和核数哪个重偠调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep 执行完后会自动苏醒。
十、为什么我们调用 start() 方法时会执行 run() 方法为什么我们不能直接调用 run() 方法?
new 一个 Thread线程和核数哪个重要进入了新建状态;调用 start() 方法,会启动一个线程和核数哪个重要并使线程和核数哪个重要进入就绪状态当分配到时間片后就可以开始运行了。start() 会执行线程和核数哪个重要的相应准备工作然后自动执行 run() 方法的内容,这是真正的多线程和核数哪个重要工莋而直接执行 run() 方法,会把 run() 方法当成一个 main
线程和核数哪个重要下的普通方法去执行并不会在某个线程和核数哪个重要zh9o执行它,所以这不昰多线程和核数哪个重要工作
总之:调用 start() 方法可启动线程和核数哪个重要并使线程和核数哪个重要进入就绪状态,而 run() 方法只是 thread 的一个普通方法还是在主线程和核数哪个重要里执行的。