专栏笔记(三)并发基础知识

并发基础知识

1、线程和进程

a.进程:程序的一次执行过程,系统运行程序的基本单位,是动态的。系统运行一个程序即是一个进程从创建、运行到小王的过程。

Java中,当启动main函数时就是启动了一个JVM进程,main函数所在的线程就是该进程中的一个线程,称主线程。

b.线程:比进程更小的执行单元,轻量级进程。同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。

c.线程和进程的关系、区别和优缺点。(从JVM角度讲述)

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

总结:线程是进程划分成的更小的运行单元。最大区别是:各进程独立,各线程不一定,因为同一进程的线程相互影响。

线程执行开销小,但不利于资源的管理和保护,而进程正相反。

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

a.程序计数器:为了在线程切换后能恢复到正确的执行位置。

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

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

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

b.虚拟机栈和本地方法栈:保证线程中的局部变量不被别的线程访问到。

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

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

c.堆和方法区 :所有线程共享的资源。

堆是进程中最大的一块内存,存放新创建的对象(所有对象都在这里分配内存)。

方法区,存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

3、并发和并行区别。

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

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

4、使用多线程的原因

a.总体上,

①计算机底层,线程轻量,程序执行的最小单位,线程间的切换和调度成本远小于进程。多核CPU中多个线程可以同时运行,减少了线程上下文切换的开销。

②IT发展趋势,多线程并发编程是开发高并发系统的基础可以提高系统整体并发能力和性能。

b.深入计算机底层

①单核时代,多线程提高CPU和IO设备综合利用率。

②多核时代,多喜爱能成提高CPU利用率。

多线程带来的问题?

并发编程是为了提高程序执行效率、提高程序运行速度,但是并不总能提高程序运行速度,且存在很多问题,如:内存泄漏、上下文切换、死锁、还受限于硬件/软件资源配置的问题。

5、线程生命周期和状态(6种)

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

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。

Java 线程的状态

Java 线程状态变迁

图中:①线程创建之后处于NEW(新建)状态;

②调用start()方法后开始运行,处于READY(可运行)状态;

③可运行状态的线程获得CPU时间片(timeslice)后处于RUNNING(运行)状态。

操作系统隐藏 Java 虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

RUNNABLE-VS-RUNNING

当线程执行 wait()方法之后,线程进入 WAITING(等待)状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到TERMINATED(终止) 状态。

6、上下文切换

当一个线程的时间片用完的时候重新处于继续状态让给其他线程使用的过程就是上下文切换。

即当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

7、线程死锁和避免

死锁:多个线程同时被阻塞,其中的一个或全部都在等待某个资源被释放。

线程死锁示意图

图中,线程A持有资源2,线程B持有资源1,当同时想申请对方资源时,两个线程会相互等待而进入死锁状态。

死锁的避免

8、sleep()方法和wait()方法的区别和联系。都可以暂停现成的执行。

最主要的区别:sleep方法没有释放锁,wait方法释放了锁。

sleep用于暂停执行。wait用于线程间交互/通信。

sleep执行结束,线程会自动苏醒。wait被调用后,不会自动苏醒,需要别的线程调用同一个对象上的notify()/notifyAll()方法。

9、start()和run()方法

为什么调用start()方法会执行run()方法,为什么不能直接调用run()方法?

new一个Thread,线程进入新建状态;调用start()方法,启动一个线程并使其进入就绪状态,当分配时间片后开始运行。

start()执行线程相应准备工作,再自动执行run()方法的内容,这是真正的多线程工作。

直接执行run()方法,会把其当成main线程下的普通方法执行,而不会在某个线程中执行它,不是多线程工作了。

总结:调用start方法可以启动线程并使线程进入就绪状态;而run方法只是thread的普通方法调用,在主线程里执行。

猜你喜欢

转载自blog.csdn.net/cheng59241/article/details/89788433