一、线程与进程
线程定义
进程中执行的一个代码段,来完成不同的任务
组成:线程ID,当前指令指针(PC),寄存器集合(存储一部分正在执行线程的处理器状态的值)和堆栈
进程定义
执行的一段程序,一旦程序被载入到内存中准备执行就是一个进程
组成:文本区域(存储处理器执行的代码)、数据区域(存储变量和进程执行期间的动态分配的内存)和堆栈(存储着活动过程调用的指令和本地变量)
例如Windows系统中运行的一个exe就是一个进程
进入任务管理器,可以查看系统运行的进程,以及每个进程中的线程数
![](https://img.haomeiwen.com/i4170765/be275f745de885cf.png)
线程与进程的关系
1. 进程是系统内存分配的最小单位,线程是系统调度的最小单位
进程拥有自己的内存空间,因为线程是属于进程的,多线程直接共享该进程中内存,提高了程序的运行效率
2. 一个程序至少有一个进程,一个进程中包括一条 or 多条线程,线程不能独立于进程
在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用 Java 命令执行一个类时,都会启动一个JVM,每一个 JVM 实际上就是在操作系统中启动了一个进程
3. 进程与线程都可以并发执行
问题:如何了解 “并发” 执行 ,它与 “并行”执行一样吗?
并行执行:从宏观和微观的角度,都是同时执行的
并发执行:从宏观角度,似乎是同时执行;但从微观角度,不是同时执行
操作系统采取时间片的机制,使多个进程(线程)快速切换执行,在宏观上就有并行执行的错觉
在单核情况下,不存在并行执行;但在多核情况下,进程(线程)分布在不同的CPU中,可以并行执行程序
二、线程的生命周期
线程是一个动态执行的过程
![](https://img.haomeiwen.com/i4170765/ace6937232e4d614.png)
1. 新建状态 New
创建线程对象,进入新建状态,此时线程属于 not alive,直到执行 start()
创建线程: 使用 new 关键字和 Thread 类或其子类, 例如:Thread t = new MyThread();
2. 就绪状态 Runnable
调用线程对象的 start() 方法,进入就绪状态,此时线程属于 alive ,但还未进入执行,只是做好了被 CPU 调度的准备
3. 运行状态 Running
当线程获取到CPU,进入运行状态,线程的 run() 方法才开始被执行,此时线程属于 alive
只有当线程处于就绪状态,才能被CPU调度,所以就绪状态是运行状态的唯一入口
4. 阻塞状态 Blocked
处于运行状态的线程,由于某种原因,放弃使用CPU,停止运行,进入阻塞状态,此时线程属于 alive
同步阻塞:
同步锁 synchronized,当某线程占有了该同步锁, 则其他线程就不能进入到同步锁中,则这些线程就会进入阻塞状态
当在阻塞队列的线程获取到同步锁时,才能进入到就绪状态等待被调度
等待阻塞:(理解得有点绕)
调用线程的 wait() ,线程进入等待状态,此时会释放占用的 CPU 资源和锁(wait()方法需要在锁中使用)
当被其他线程调用 notify() 唤醒之后,需要重新获取对象的锁,所以会先进入Blocked状态,才会进入就绪状态
其他阻塞:
调用线程的 sleep() 或 join() 或 发出了I/O请求,线程会进入到阻塞状态
当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新进入就绪状态
5. 死亡状态 Dead
当一个线程的 run() 方法运行完毕 or 被中断 or 被异常退出,该线程进入死亡状态
三、线程的创建
- 实现 Runnable 接口,实例化 Thread 类(线程无返回值)
- 继承 Thread 类,重写 Thread 的 run() 方法(线程无返回值)
- 实现 Callable 接口,通过 FutureTask 包装器创建线程(线程有返回值)
1. 实现 Runnable 接口,实例化 Thread 类(线程无返回值)
step1: 创建一个类,例如 RunnableThread,实现 Runnable 接口
![](https://img.haomeiwen.com/i4170765/eff6b25544eb41f2.png)
step2: 实例化 RunnableThread 对象, 创建 Thread 对象,将 RunnableThread 作为参数传给 Thread 类的构造函数,然后通过 Thread.start() 方法启动线程
![](https://img.haomeiwen.com/i4170765/40a69404492e9fe4.png)
运行结果
![](https://img.haomeiwen.com/i4170765/9187413d59493c03.png)
问题: 为什么创建 RunnableThread 对象后,需要将它和 Thread 对象进行关联?
查看 Runnable 接口的源代码,可以看到 Runnable 接口只有一个 run() 方法,所以需要通过 Thread 类的 start() 方法来启动线程
启动线程后,Thread 类中的 run() 方法会先判断传入的 target Runnable 对象的 run() 方法是否为空,若不为空,则调用 target Runnable 对象的 run() 方法
![](https://img.haomeiwen.com/i4170765/35ef87533568eaa8.png)
而且,RunnableThread 类实现 Runnbale 接口中不能直接使用 Thread 类中的方法,需要先获取到Thread 对象后,才能调用 Thread 方法
2. 继承 Thread 类,重写 Thread 的 run() 方法(线程无返回值)
step1: 创建一个类,例如 MyThread,继承 Thread 类,重写 Thread 的 run() 方法
![](https://img.haomeiwen.com/i4170765/2a9d113a12aa5ae5.png)
step2: 实例化 MyThread 对象,直接调用 start() 方法启动线程
![](https://img.haomeiwen.com/i4170765/57a8cdcedb64a0d0.png)
运行结果
![](https://img.haomeiwen.com/i4170765/897dfcdf124d696c.png)
问题:实现 Runnable 接口 和 继承 Thread 类,运行结果不一样,这是为什么?
继承 Thread 类和实现 Runnable 接口实现多线程,会发现这是两个不同的实现多线程
继承 Thread 类是多个线程分别完成自己的任务
实现 Runnable 接口是多个线程共同完成一个任务,其实用继承Thread类也可以实现,只是比较麻烦
这样的话,实现 Runnable 接口比继承 Thread 类具有一定的优势
1)适合多个相同的程序代码的线程去处理同一个资源
2)可以避免 Java 中的单继承的限制
当一个类继承 Thread 类后,则不能在继承别的类,而接口比较灵活,可以实现多个接口,而且实现接口了还可继续继承一个类
3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
参考链接:https://www.cnblogs.com/CryOnMyShoulder/p/8028122.html
https://www.cnblogs.com/xubiao/p/5418141.html
3. 实现 Callable 接口,通过 FutureTask 包装器创建线程(线程有返回值)
step1: 创建一个类,例如 CallableThread,实现 Callable 接口,重写 Callable 接口的 call() 方法
![](https://img.haomeiwen.com/i4170765/a0927cf4381dfc6a.png)
step2: 实例化 CallableThread 对象,使用 FutureTask 类来包装 CallableThread 对象
然后 FutureTask 对象作为参数传给 Thread 类的构造函数,通过 Thread.start() 方法启动线程
使用 FutureTask.get() 得到 Callable 接口的 call() 方法的返回值
![](https://img.haomeiwen.com/i4170765/a9f94d4737c7a473.png)
返回结果
![](https://img.haomeiwen.com/i4170765/f0788ac525d3c73e.png)
Callable 和 Runnable 相似,类实例都需要被 Thread 执行,但 Callable 接口能返回一个值或者抛出一个异常,Runnable 不能
实现 Callable 接口需要重写其唯一的 call() 方法
FutureTask 实现了 Runable 接口 和 Future 接口,所以如果想 Callable 实例作为 Thread 的执行体就必须通过 FutureTask 来作为桥梁
网友评论