一、创建线程的方式
1.使用Thread重写run
2.实现Runnable接口,重写run
搭配Thread类才能真正在系统中创建线程。
3.继承Thread,重写run,使用匿名内部类
4.实现Runnable,重写run,匿名内部类
5.lambda表达式(匿名函数/方法)
lambda表达式这个写法相当于实现了Runnable重写run,lambda代替了Runnable的位置。编译器编译时,Thread有好多版本,依次匹配,Runnable能匹配上,有run方法,无参数,正好和lambda匹配。
二、Thread其他常见的属性和方法
1.Thread的常见方法:
1.Thread():创建线程对象
2.Thread(Runnable target):使用Runnable对象创建线程对象
3.Thread(String name):创建线程对象,并命名
4.Thread(Runnable target,String name)使用Runnable对象创建线程并命名
5.Thread(ThreadGroup group,Runnable target)线程可以被分管理=>线程组
该线程组为java中的概念,与系统内核中的线程组不是一个东西。
2.Thread的常见属性:
1.ID getId():
JVM自动分配的身份标识,保证唯一性
2.名称 getName()
3.状态 getState():
进程中有就绪状态和堵塞状态。Java对线程状态进一步区分(比系统原生状态更丰富)
4.优先级 getPriority():
线程的优先级,在Java中,设置优先级效果不明显<=>系统随机调度
5.是否有后台线程 isDaemon():
daemon守护,守护线程。例如:有两个线程,main已经结束了,t还在执行,但进程仍在继续执行,直到Process finished...才是进程结束。
创建线程,默认为前台线程=>阻止进程结束=>只要前台线程没执行完,进程就不结束(即使main已执行完毕)
t.setDaemon(true)在strat之前,设置线程为后台线程(不能在strat后设置)
6.是否存活 isAlive():
内核中的线程(PCB)是否还存在。
Java代码中定义的线程对象(Thread)实例,虽然表示一个线程,但这个对象本身的生命周期和内核中的PCB生命周期是不完全一样的。
Thread t = new Thread();=>t对象有了,但内核PCB还没有,isAlive为false
t.start();=>真正在内核中创建出该PCB,isAlive为true
当线程run执行完了,内核中的线程就结束了(内核PCB释放)isAlive为false,但t变量可能还在
7.是否被中断/终止 isInterrupted()
使用Thread实例内部自带的标志位,代替刚才手动创建的isQuit变量。
三、启动线程:start()
调用strat创建出新线程,本质=>start调用系统api来完成创建线程的操作
run和start的区别
t.start():创建一个新的线程,由新的线程执行操作
t.run():还是在主线程中执行操作
四、终止线程:interrupt()
代码一:使用isQuit
isQuit不能作为main方法中的局部变量。lambda表达式中:变量捕获
lambda表达式/匿名内部类可以访问到外面定义的局部变量。但是捕获的变量的是final/事实final。
(匿名内部类访问外部的成员,不受到变量捕获的影响)
由于此处isQuit确实要修改,不能写成final,也不是事实final。
Java对于变量捕获有final的限制
isQuit是局部变量时,是属于main方法的栈帧中的,但是Thread lambda有自己独立的栈帧(不同线程)=>生命周期不同
导致:main方法执行完了,栈帧销毁了,但Thread栈帧还在,想继续使用isQuit。
解决方法:变量捕获本质上就是传参。=>让lambda表达式在自己的栈帧中创建一个新的isQuit并把外面的isQuit拷贝过来(为了避免里外isQuit的值不同步,java就不让修改isQuit)
代码二:让Thread对象内置变量
使用Thread实例内部自带的标志位代替刚才手动创建的isQuit变量。
在执行sleep的过程中,调用interrupt,有可能sleep休眠时间还不到被提前唤醒。
提前唤醒:1.抛出InterruptedException,被catch获取到
2.清除Thread对象的isInterrupted标志位
=>通过interrupt方法,将标志位设为true,但sleep提前唤醒又设为false,若想让线程结束,在catch中加上break。
在Java代码中,会以异常的形式体现问题。可通过catch代码,对这些异常进行处理。
①尝试自动恢复,如出现一个网络通信相关的异常,在catch尝试重连网络。
②记录日志,(将异常信息记录到文件中)不立即解决
③发出警报,针对一些比较严重的问题
④有少数的正常业务逻辑,会依赖到catch,如文件操作中有些方法,就是要用catch来结束循环。
在Java中,线程的终止是一种“软性”操作,必须要对应的线程配合才能终止。
系统原生的api还提供了强制终止线程的操作 =>无论线程是否配合,无论线程执行到哪个代码,都能强行终止线程。 但在Java的api中没有提供。(弊大于利)
如果强行中断一个线程,很可能线程执行到一半,会出现一些残留的临时性质的“错误”数据。
五、等待线程(结束):join()
多个线程的执行顺序是不确定的(随机调度,抢占式执行)
线程底层的调度是无序的,但可以通过一些api来影响到线程执行顺序。
join方式:影响线程结束的先后顺序(使线程堵塞)
join<=>线程最核心的api之一,任何一个线程都可调用。
代码一:
t.join():让main线程等待线程结束;执行join时,看t线程是否在执行,若t在执行,main线程就会堵塞(不会参与cpu执行);t线程运行结束,main线程就会从堵塞中恢复,继续执行。
1.死等
2.带有超时时间的等,带有一个时间上限。
代码二:
让主线程创建一个新线程,由新线程完成运算,主线程获取结果
把整个运算分成两段,分别运算。(提升速度)
但线程数不能无线提升,一定程度后无法提升速度。