阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!7000字长文,希望本文内容能够帮助到你!
目录
一:创建线程五种方式
方式一:继承Thread类,再实例化
方式二:实现Runnable接口,重写run方法
方式三:匿名内部类写法
方式四:Runnable+匿名内部类
方式五:lambda表达式
二:Thread常见方法
1:Thread方法
2:获取Thread属性的方法
(1)守护进程
(2).setDaemon()方法
①前台/后台线程
②.setDaemon
③结果分析:
(3)isAlive()
(4)start方法和run方法的区别
三:如何提前终止一个线程
1:标志位——isQuit
(1)变量捕获:重点重点重点
(2)lambda复制和传参理解
2:Thread内置变量isinterrupted
(1)isinterrupted本质
(2)sleep清空标志符
(3)解决方式(catch中加代码)
一:创建线程五种方式
方式一:继承Thread类,再实例化
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-17* Time: 14:20*/
class MyThread extends Thread{@Overridepublic void run() {System.out.println("这就是进入该线程的入口");}
}
public class ThreadDemo1 {public static void main(String[] args) {//根据类,创建实例,线程实例才是真正的线程//一般用向上转型的写法Thread t = new MyThread();t.start();}
}
方式二:实现Runnable接口,重写run方法
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 9:20*/
class MyTread3 implements Runnable{@Overridepublic void run() {while(true){System.out.println("这里是run线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class TreadDome3 {public static void main(String[] args) {Runnable runnable = new MyTread3();//相当于借刀杀人了Thread t = new Thread(runnable);t.start();while(true){System.out.println("这里是main函数线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
注:可以通过Runnable这个接口,可以抽象出一段被其他实体执行的代码,还是需要搭配Thread类来进行使用
方式三:匿名内部类写法
在实例化Thread对象时{}里创建匿名内部类,重写run方法
匿名内部类:
①没有名字,不能重复使用
②这个新的类继承自Thread,并且{}里可以定义子类的属性和方法
③t的指向不仅仅是Thread,还有它的子类(虽然没名字),(即不仅是Thread的实例,也是其子类的实例)
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 9:39*/ public class ThreadDome4 {public static void main(String[] args) {Thread thread = new Thread(){public void run(){while(true){System.out.println("这里是由匿名内部类实现的线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};thread.start();while(true){System.out.println("这里是main函数入口");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}} }
方式四:Runnable+匿名内部类
实现Runnable接口,重写run方法实现匿名内部类
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 10:07*/
public class ThreadDome5 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("run method");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});thread.start();while(true){System.out.println("main method");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
方式五:lambda表达式
lambda表达式,适合简单直观的代码,如果代码过于复杂还是需要提取出来的
在Java中方法的实现依赖于类,方法不能脱离类单独存在,这里就导致为了设置回调函数,不得不套上一层类,但是并不常用——引出了lambda表达式。
函数式接口相当于在没有破坏java原有的规则上(方法不能脱离类单独存在),单独给lambda一个解释
第一个标记的红色方框中的()-> ,()括号中可以带参数
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 10:29*/
public class ThreadDome55 {public static void main(String[] args) {Thread thread = new Thread(()->{while (true){System.out.println("run method");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while(true){System.out.println("main method");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
二:Thread常见方法
1:Thread方法
2:获取Thread属性的方法
后面的代码中会运用到,有相应的解释
(1)守护进程
通过之前的学习,我们知道,main方法走完了,整个进程就结束了。 现在我们引入了线程的概念,那这个结论还准确吗。有没有例外呢?
下面我们举例:代码里面我们创建main函数线程
这里我们使用jconsole工具来辅助(上一篇博客有讲怎么使用jconsole前面)
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 10:58*/
public class ThreadDome6 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("run method");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}} , "这个线程的名字叫:测试");thread.start();}}
我们可以发现jconsole线程运行详细信息中,没有main函数,但是有我们的测试线程,很明显即使main函数这个前台线程已经结束了,但是这个进程依旧还在运行(可以说是这个“这个线程的名字叫:测试”的线程还在运行)
由此我们引出一组概念:前台线程和后台线程
(2).setDaemon()方法
①前台/后台线程
重点:咱们创建的线程,默认都是前台线程,会阻止进程的结束,只要前台线程没有执行完,进程就不会结束,即使main函数这个线程已经执行完毕
(这么理解:后台进程没有话语权,只要还有前台线程,进程就不会结束)
②.setDaemon
理解:Daemon是守护的意思,这个方法是把某线程设置为后台线程(守护线程)
注:这个方法一定要在“.start”方法之前设置
接上文,我们引入.setDaemon()方法,可以将Thread下的线程从前台改为后台,下面会详细解释两者区别。
③结果分析:
什么都没有打印是因为,我们把main函数设置为了后台线程,这个线程走完了,那整个进程都结束了,Thread这个前台线程
(3)isAlive()
isAlive(),方法,表示了当前内核中的线程(pcb)是否还存在,内核中的线程和实例的周期是不一致的
①在创建完Thread对象之后,start方法之前,pcb还没有创建出来,所以答疑结果为false
②在start方法之后,线程结束之前,对象存在,pcb存在,打印结果为true,
③线程run完了,pcb得到释放,为false
④注意下面的代码Thread.sleep(5000) ,如果沉睡时间改为3000ms以下,那么最后一条打印结果就不确定了,线程的随机调度
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 18:24*/
public class ThreadDemon8 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println(thread.isAlive());//false,有对象,无pcb线程thread.start();//创建线程System.out.println(thread.isAlive());//true,有对象,有pcbThread.sleep(5000);//main函数线程沉睡5s,此时Thread线程3s沉睡完了,thread线程结束,thread对象还在System.out.println(thread.isAlive());//false}}
(4)start方法和run方法的区别
start方法核心是创建一个新的线程,但是run方法仅仅是一个方法、一个线程的入口,类似main方法的入口,两者不能说没有关系,简直是毫无关系!!!
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 18:56*/
public class ThreadDemon9 {public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("run线程方法正在执行");}}};//t1.run();//不屏蔽这行代码,将会一直死循环在run方法中,单线程main的t1.start();System.out.println("main函数线程正在执行");}}
三:如何提前终止一个线程
1:标志位——isQuit
我们需要在Thread实例化中写符合能够控制线程的结构,下面的代码我们引入isQuit这个静态成员变量,来控制while循环,进而达到提前终止线程这样一个目的
/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-19* Time: 19:18*/
public class ThreadDemon12 {private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while(!isQuit){try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1线程正在运行");}});t1.start();Thread.sleep(2001);isQuit = true;System.out.println("终止t1线程");}}
(1)变量捕获:重点重点重点
提问:上述代码能否将isQuit这个变量在main方法中定义(局部变量)。不行
在lambda匿名内部类当中有一条规定:lambda匿名内部类可以获取外面定义的成员变量(本质是将外面的成员变量作为参数传进来,这个被捕获的(变量)参数必须是final,或者“事实final”)
大前提这个变量定义在main函数内部
零:解释“事实final”
定义的成员变量虽然没有被final修饰,但是这个成员变量没有被修改过(有点那种“虽无夫妻之名,但有夫妻之实”的感觉)。
举例1:在mian函数内部定义 不加final修饰 的局部变量(可行)
举例2:在mian函数内部定义 不加final修饰 的局部变量 并且修改isQuit的值(不可行,lambda报错)
举例3:在mian函数内部定义 加final修饰 的局部变量 并且修改isQuit的值(不可行,final报错)
(2)lambda复制和传参理解
承接举例1:我们删除isQuit这一行代码
我们都知道,main函数执行完毕,那么main函数中定义的局部变量会进行回收,但是在上述两个线程中,main方法结束了,isQuit已经被回收了,这个Thread(t1)线程为什么还能运行(他线程用到了isQuit作为循环判断条件)。
总结:这里是因为,lambda获得的成员变量,是作为参数传进来的(可以理解为复制拷贝,是一个全新的个体),main方法执行完了,isQuit的栈帧就销毁了,但是Thread(t1)中拷贝过来的isQuit所创建的栈帧还在呀!!!
。
2:Thread内置变量isinterrupted
(1)isinterrupted本质
利用Thread类的实例内部成员变量,来取代我们在1中使用的,手动创建的静态成员变量isQuit
package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-20* Time: 10:23*/
public class ThreadDemon13 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while(!Thread.currentThread().isInterrupted()){ //当t1.interrupt();生效,循环条件为false,被打断了try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1线程正在运行");}});t1.start();Thread.sleep(2001);t1.interrupt();//让t1被打断,相当于isQuit = trueSystem.out.println("终止t1线程");}
}
(2)sleep清空标志符
结果分析——
目的:给程序员创造更多的“可操作空间”
运行上述代码,报错,这是因为,我们的中断了t1线程的sleep(提前唤醒会做两件事:1:清空标志符,2:抛出InterruptedException异常),sleep把interrupted又设置回false,进而while循环判定条件变成true,最后被catch捕获中断异常。
(3)解决方式(catch中加代码)
①让线程立即结束:加上break;
②让线程不结束:不加break,让它报错
③让线程执行一些代码后再结束:写一些其它代码后,再break。