线程与线程池的关系
线程是执行任务的最小单元,单个线程可以启停,从而执行任务。
线程是工厂中的工人, 工人可能自己有设计方法(自己写run方法),也可能使用图纸中的设计方法(任务中的run方法)
任务是工厂承接的设计任务,任务中包括图纸(run方法或call方法)
线程池是工厂,每过来一个任务,工厂就要启动工人生产产品。
工厂为了按图纸生产产品,可以配置启动多少个工人(核心线程数、最大线程数)
工厂接受任务的运营模式是什么(线程池队列)。
可以配置如果任务太多如何决策(抛弃策略)。
线程的基础知识
(1)Thread线程的状态
见源码部分
(2)Thread线程的中断
thread.interrupt()方法只会改变线程的中止状态位,但线程是否立即中止,由线程本身判断。new和terminated对于中断操作几乎是屏蔽的,runnable和blocked类似,对于中断操作只是设置中断标志位并没有强制终止线程,对于线程的终止权利依然在程序手中。waiting/timed_waiting状态下的线程对于中断操作是敏感的,他们会抛出异常并清空中断标志位。
thread.isInterrupted可以判断线程当前标志位是否是中止态
利用isInterrupted和return可以实现判断并彻底中止线程
Thread源码
线程状态综述
线程的内部类Status代表线程状态:
获取线程状态
使用jstack可以获取线程状态
WAITING和BLOCKED状态区别
synchronized会导致线程进入Blocked状态,Object.wait()导致线程进入Waiting状态,Waiting线程调用了notify()方法之后,可能会直接获取synchonized锁到达可运行Runnable,但是如果没有获取到synchonized锁的时候应该进入Blocked状态
状态转换常用方法包括:
RUNNBALE状态
表示线程是可以执行的,就等cpu进行调度了。抢占到cpu时间片资源就可以执行了。有两个子状态:READY就绪和RUNNING运行中就绪到运行是通过cpu调度,而RUNNING到READY是通过Thread.yield()主动释放
核心属性
Thread里面封装了一个Runnable对象target,Thread本身就是一个Runnable对象,为什么还要封装一个Runnable进去,而且在下面构造的时候还要传一个runnable对象进去呢?
这是为了实现执行器和任务的分离。Thread是任务执行的执行器,target是真正的可执行对象,如果二者分离,才能真正实现在线程池流程中thread的复用,如果二者绑定了,那一个任务就绑死在一个线程上了,这个任务执行完成,这个线程就要跟着销毁,不符合线程池和复用的设计理念。
构造方法
Thread的核心成员变量包括:
有一个空参构造,一个带参构造,都是最终调用到init方法中。
可以看到二者的区别在于带参构造是传入了一个Runnable可执行单元做target的,这就使得线程和作业分离。
在不指定名称的情况下,线程Thread会自动默认为“Thread-”的名字,如果使用线程工厂,就可以自己指定名称。
init方法
start - java多线程启动入口
Thread提供了start方法,是java多线程启动的入口。
首先判断status,0代表NEW状态,如果不是new状态,不允许thread执行start方法。
最核心的在这里,执行start0方法。start0是一个native方法,它的实现包括:
首先,它会检查当前线程对象的状态是否允许启动。如果线程对象的状态不满足启动条件,start0()方法将抛出一个IllegalThreadStateException异常。
接下来,start0()方法会为新线程分配所需的系统资源和内存空间。这包括为线程创建一个独立的执行环境和堆栈空间。
一旦系统资源和内存分配完成,start0()方法将调用操作系统提供的原生函数,以便创建一个真正的操作系统级线程。这个原生函数可能是基于平台的API,比如Windows的CreateThread()或Linux的pthread_create()。
最后,start0()方法会更新线程对象的状态,并且将线程添加到线程调度器中,使其可以被调度执行。
同时,在start0的执行过程中,JVM会自动调用线程thread的run方法。因此在jdk源码中,实际看不到run方法的调用,但其实在start方法触发时就执行了run方法。
run - 线程执行单元
集成Runnable接口的Thread类实现了Runnable的run方法。这里思考,为什么不是Thread直接自带run方法?
这样可以实现线程和可执行单元的分离。如果线程自带run,那么创建多个可执行任务就是多个thread,只能一个执行一个,而通过创建一个Runnable,可以给多个线程执行,对一个Runnable进行并发处理。例如银行取号机,四台取号机共用一天的1-50号码序列,要对一个可执行单元做并发。
Thread实现的run方法实际就比较简单了:
相当于它将执行内容直接交给了可执行单元去实现。这就是典型的策略模式,执行器和可执行单元分离。
线程死锁
死锁的概念
死锁是指两个线程同时拥有对方请求的资源,导致均永远无法执行下去。
synchronized (资源) {}中的代码段在执行时保有对资源的占用,其他线程想要使用资源A必须先请求锁。
解决死锁的方法
顺序锁
将线程对资源的请求由互斥改为顺序,如下图:
加图
锁超时
上示例lockB.tryLock()获取不到会报中断异常,释放锁A,然后执行try语句等待1秒,再重新while true循环
Runnable和Callable
实现Runnable接口/Callable接口表明此对象是一个可执行任务
二者的区别在于:
run()实现自runnable接口,call()实现自callable接口
callable 和runnable 很像, callable 的call() 方法可以等同于runnable 的run() 方法,
call() 能返回结果,run() 不能。
call() 可以抛出受检查的异常,比如ClassNotFoundException, 而run()不能抛出受检查的异常。
评论区