一. 进程/线程/线程上下文切换
CPU巧妙地运用了时间片轮转地方式,给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载,这段过程就叫做上下文切换。
1. 进程
进程是指一个程序运行的实例。进程的特征如下:
- 动态性
进程是程序的一次执行过程,临时且有生命期,是动态产生,动态消亡的 - 并发性
任何进程都可以同其它进程一起并发执行 - 独立性
进程是系统进行资源分配和调度的一个独立单位 - 结构性
进程有程序、数据和进程控制块三部分组成,
程序用于描述进程要完成的功能,是控制进程执行的指令集;
数据集合是程序在执行时所需要的数据和工作区;
程序控制块(PCB,Program Control Block),包含进程的描述信息和控制信息,是进程存在的唯一标志;
2. 线程
是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间,也就是所在进程的内存空间。
一个标准的线程由线程ID、当前指令针织、寄存器和堆栈组成;
3. 线程与进程的区别
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间及一些进程级的资源,某进程内的线程在其它进程不可见;
调度和切换,线程上下文切换比进程上下文切换要快得多。
4. 上下文
是指某一时间点CPU寄存器和程序计数器的内容。
5. 寄存器
CPU内部的数量较少但是速度很快的内存(与之对应的时CPU外部相对较慢的RAM主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
6. 程序计数器
是一个专用的寄存器,用于表明指令序列中CPU正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统。
7. PCB-进程控制块
上下文切换过程中的信息保存在 进程控制块(又称为”切换桢“)中,信息会一直保存到CPU的内存中,直到它们被再次使用。
8. 上下文切换
上下文切换可以认为是内核在CPU上对于进程(包括线程)进行切换。
8.1. 上下文切换的活动
(1). 挂起一个进程,将这个进程在CPU中的上下文存储与内存中的某处;
(2). 在内存中检索下一个进程的上下文并将其在CPU的寄存器中恢复;
(3). 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序中。
8.2. 引起线程上下文切换的原因
- 当前执行任务的时间片用完之后,系统CPU正常调度下一任务;
- 当前执行任务碰到IO阻塞,调度器将此任务挂起,继续下一任务;
- 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;
- 用户代码挂起当前任务,让出CPU时间;
- 硬件中断
二. 线程实现/创建方式
- 继承Thread类
本质上实现了Runnable接口的一个实例,代表一个线程的实例; - 实现Runnable接口
避免了单继承的限制; - ExecutorService、Callable、Future有返回值线程
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取返回的Object; - 基于线程池的方式
三. 线程的运行流程
1. 新建状态
new一个线程对象就进入了新建状态,此时仅有JVM为其+ 分配内存,并初始化其成员变量的值;
2. 就绪状态
调用start()方法则进入了就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行;
3. 运行状态
分配到CPU的时间片段后,执行run方法,进入运行状态;可能会转回就绪,等待下一次CPU分配时间片段,继续执行;
4. 阻塞状态
线程因为某种原因放弃了cpu使用权,让出cpu timeslice,暂时停止运行。
- 等待阻塞【wait方法】
- 同步阻塞【synchronized】
- 其它阻塞【sleep、join或者I/O请求】
5. 死亡状态:
死亡的三种方式如下:
- 正常结束【run()或call()方法执行完成】
- 异常结束【线程抛出一个未捕获的Exception或Error】
- 调用stop【容易导致死锁】;
四. 线程基本方法
1. wait
一般用在同步方法或同步代码快中。
调用该方法的线程会释放对象的锁,进入WAITING-等待状态,只有等待另外线程的通知或中断才会返回。
2. sleep
sleep方法使当前线程休眠,进入TIMED-WATING状态,它不会释放当前占有的锁。
3. yield
使当前线程让出CPU执行时间片,与其它线程一起重新竞争CPU片。一般情况下,优先级高的线程有更大可能性成功竞争得到CPU时间片,但非绝对,有的操作系统对线程优先级并不敏感。
4. interrupt
中断一个线程。其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态。
- 调用 interrupt() 方法并不会中断一个正在运行的线程。也就是说处于非阻塞状态的线程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
- 调用 sleep() 而使线程处于阻塞状态,这时调用interrupt() 方法,会抛出 InterruptedException,从而使线程提前结束阻塞状态。但在抛出异常前,会清除中断标识位,** 所以抛出异常后,调用 isInterrupted() 方法将会返回false。
- 中断状态时线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程的时候,可以调用 tread.interrupt() 方法,在线程的run方法内部可以根据 thread.isInterrupted() 的值来优雅的终止线程。
5. join
等待其它线程终止,在当前线程中调用一个线程的 join() 方法,当前线程转为阻塞状态,等待另一个线程结束,当前线程再由阻塞状态变为就绪状态。
很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,例如主线程需要在子线程结束后再结束,这时可用到join()方法。
6. notify
线程唤醒,Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个wait方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其它所有线程进行竞争。类似的方法还有notfyAll(),唤醒在此监视器上等待的所有线程。
7. 其它方法
- isAlive():判断一个线程是否存活
- activeCount():程序中活跃的线程数
- enumerate():枚举程序中的线程
- currentThread:得到当前线程
- isDaemon():一个线程是否为守护线程
- setDaemon():设置一个线程为守护线程
- setName():为线程设置一个名称
- setPriority():设置一个线程的优先级
- getPriority():获得一个线程的优先级
8. sleep与wait区别
sleep方法属于Thread类,wait方法属于Object类;
sleep方法导致了程序暂停执行指定的时间,让出cpu给其它线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,wait方法会使对象进入此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态;
调用sleep方法的过程中,线程不会释放对象锁,而调用wait方法的时候,线程会放弃对象锁;
9. start与run区别
start方法实现多线程运行,无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;
调用start方法,线程处于就绪状态,并没有运行;
run方法称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行run函数当中的代码;
五. 终止线程4种方式
- 正常运行结束
程序运行结束,线程自动结束。 - 使用退出标志退出线程
定义一个Boolean类型的标志,然后通过volatile关键字修饰,使该标志同步,在同一时刻只能由一个线程来修改它的值 - Interrupt方法结束线程
- Stop方法终止线程
强行终止,不安全的操作;
创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何加锁的代码块,都是为了保护数据的一致性,如果在调用stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其它线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。
六. Java后台线程-守护线程
1. 定义
守护线程-也称”服务线程“,为用户线程提供公共服务,在没有用户线程可服务时会自动离开;
2. 优先级
守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务;
3. 设置
通过 setDaemon(true) 来设置线程为”守护线程“,将一个用户线程设置为守护线程的方式是在线程对象创建之前用线程对象的setDaemon方法;(注:在Daemon线程种产生的新线程也是Daemon的);
4. 线程是JVM级别的
在web应用中启动一个线程,即使你停止了web应用,这个线程依然是活跃的;
5. 生命周期
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生地事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统”同生共死“。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了,如果还有一个或以上的非守护线程则JVM不会退出。
6. 举个栗子
垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。