内容导航
从操作系统的发展了解进程、线程模型
线程的优势
线程的应用场景
线程的生命周期
线程的启动实现原理
一、了解进程、线程模型
每学习一个新的技术,我们都需要先去了解这个技术的背景,这个过程看似浪费时间,其实对于后续的学习过程中,能够促进理解很多问题。对于线程这个概念,可以先从操作系统讲起。因为操作系统的发展带来了软件层面的变革。从多线程的发展来看,可以将操作系统的发展分为三个历史阶段。
1.真空管/穿孔打卡
工作流:把程序写在纸上,然后穿孔成卡片,再把卡片盒带入专门的输入室。输入室的操作人员将卡片程序输入到计算机上。计算机运行完当前任务以后,把计算结果从打印机上进行输出,操作人员把打印的结果送入到输出室给到程序员。
最大的问题是计算机在等待程序输入的时候,会处于空闲状态。
在这里插入图片描述
2.晶体管/批处理操作系统
工作流:在输入室收集全部的作业,然后用一台比较便宜的计算机把它们读取到磁带上。然后把磁带输入到计算机,计算机通过读取磁带上的指令运行运行,将结果输出到磁带上。
批处理操作系统虽然解决了计算机空闲的问题,但是当某一个作业因为等待磁盘或者其他I/O操作而暂停,那CPU就只能阻塞知道该I/O完成,对于PCU操作密集型的程序,IO操作相对较少,因此浪费的时间也很少。但是对于IO操作密集的场景,CPU的资源属于验证浪费。
在这里插入图片描述
3.集成电路/多道程序设计
(1)引入进程的概念,进程之间相互隔离,计算机可以加载多个进程,程序运行时系统会创建一个进程,并且给每个进程分配独立的内存地址空间,如果进程阻塞,可以快速切换其他进程,并且保证进程之间不会相互干扰。对于单核CPU,CPU通过时间片快速切换进程来实现多个进程”并发“的假象
在这里插入图片描述
(2)CPU从单核发展到多核,可以真正并行执行多个进程。而且,一个进程可能会有多个任务,而这些任务并不是串行的关系,需要并发执行,所有就引进了线程的概念。线程是一种轻量级的进程,其创建、销毁和切换的成本相对来说很小。
在这里插入图片描述
硬件的更新换代带来的软件的发展。
二、Java线程的创建方式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
三、线程的应用场景
其实对于我们平时日常基于业务开发老说,很少有应用场景用到多线程了,很多异步场景我们都可以通过分布式消息队列来处理。但是我们看一些框架的源码,会发现线程无处不在。
业务中比较多的场景比如文件跑批,每天会有一些比如收益文件、对账文件,我们会有一个定时任务拿到数据后通过线程去处理,还有广告竞拍系统(ADX)中,对接入的每家DSP广告服务起一个线程同时发起竞标请求进行竞价投标等。
对于Zookeeper源码中也有一个比较有意思的异步责任链模式,简化后
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上述案列中,首先将业务线程类和日志线程类按指定链路初始化启动。每次请求将请求对象添加到阻塞对象中,就结束了这次请求。
业务操作链和日志操作链的线程一直在后台处理阻塞对象中的请求对象。这样能够极大的提高响应。
四、线程的生命周期
Java线程既然能够创建,那么势必也能被销毁,所有线程是存在生命周期的,那么我们接下来从线程的生命周期开始去了解线程。
NEW
新建状态,新创建了一个线程对象,还未调用线程的start()方法。
RUNNABLE
运行状态,就绪状态的线程获取了CPU,执行程序代码的状态,还有种可能就是这个线程正在等待其他的系统资源(IO资源等),这种状态也称为Running状态,Runnable状态和Running统一称为Runnable状态。
BLOCKED
阻塞状态,一个线程因为等待监视锁而被阻塞的状态。阻塞的线程不会被分配CPU资源,这个时候线程被操作系统挂起。 阻塞状态的线程,即使调用interrupt()方法也不会改变其状态。
线程因为某种原因放弃了CPU使用权,阻塞分几种情况
1、等待阻塞:运行的线程执行wait方法,jvm会把当前线程放到等待队列
2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用了,那么jvm会把当前线程放到锁池中
3、其他阻塞:运行的线程执行Thread.sleep或者t.join方法,或者发出了IO请求,jvm会把当前线程设置为阻塞状态,当sleep结束、join线程终止、io处理完毕则线程恢复
WAITING
无条件等待,一个正在等待的线程的状态,当线程调用wait()/join()/LockSupport.park()不加超时时间的方法之后所处的状态,如果没有被唤醒或等待的线程没有结束,那么将一直等待,当前状态的线程不会被分配CPU资源和持有锁。
TIMED_WAITIN
等待超时状态,一个线程在一个特定的等待时间内等待另一个线程完成一个动作会在这个状态,超时以后自动返回。
TERMINATED
终止状态,表示当前线程执行完毕
在这里插入图片描述
五、线程启动的原理
我们通过调用线程的start方法来启动一个线程,线程启动后会调用run方法执行业务逻辑,run方法执行完毕后,线程的生命周期也就终止了,我们可能很疑惑,启动一个线程为什么是调用start方法,而不是run方法,我们简单分析一下,看一下start方法的定义
在这里插入图片描述
我们看到调用start方法实际是调用了一个native的方法start0()来启动一个线程,首先start0()这个方法是在Thread的静态快中来注册的,代码如下:
在这里插入图片描述
这个registerNatives的作用就是注册一些本地方法提供给Thread类来使用。
所以结论是,java里面创建线程后必须要调用start方法才能真正的创建一个线程,该方法会调用虚拟机启动一个本地线程,本地线程的创建会调用当前系统创建线程的方法进行创建,并行线程的执行的时候会回调run方法进行逻辑的处理。
六、线程的终止方法及原理
线程的终止有主动和被动之分,被动标识线程出现了异常或者方法执行完毕,线程会自动终止。
主动的方式是Thread.stop()来实现线程的终止,不过stop()是一个过期的方法,原因是stop()方法在终止一个线程的时候不确保线程的资源正常释放,也就是不会给线程完成资源释放的计划。那么我们如何安全的终止一个线程呢?
在这里插入图片描述
注意以下两点:
1、Thread.currentThread().isInterrupted() 判断线程中断的标识
2、thread.interrupt() 改变中断标识的值
在这里插入图片描述
在这里插入图片描述