当前位置: 首页 > news >正文

【JAVA EE初阶】多线程(1)

这样的代码,虽然也能打印hello thread,但是没有创建新的线程,而是直接在main方法所在的主线程中执行了run的逻辑

start方法,是调用系统api,真正在操作系统内部创建一个线程。这个新的线程会以run作为入口方法(执行run)的逻辑,run方法,不需要在代码中显式调用

此处遇见受查异常为什么不能用throws呢?

因为,如果给run方法声明加上throws,此时就意味着和父类Thread的run的方法声明不一致,就无法构成方法重写了。

package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 创建并启动MyThread线程MyThread myThread = new MyThread();myThread.start();// myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}

输出:

但是如果换成这样的代码:

package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 创建并启动MyThread线程MyThread myThread = new MyThread();// myThread.start();myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}

输出:

由此可见run方法并没有创建一个新的进程,从始至终只有一个主线程在运行,只有将run的方法执行完才能去打印hello main 但是由于这里的 run方法是一个死循环,所以一直没有打印hello main

通过观察我们可以发现这里的main和thread的打印,并不是严格交替的。由于两个打印,都加了sleep(1000)。当1000ms时间到的时候,这两个线程哪一个限制性,这个顺序是不确定的。

(操作系统调度线程的顺序是无需,不可预测的,随机的 )

可以通过Java jdk中的jconsole工具来观察进程

我的jdk-jconsole工具目录:C:\Program Files\Java\jdk-17\bin

折线图

这张图列出了当前进程中所有的线程。剩下的线程,都是JVM自带的,这些线程进行了一些背后的操作,比如负责垃圾回收,记录统计信息,记录一些调试信息。

如果将Thread.start()改成Thread.run()——>

我们会发现刚刚的Thread0没有了

一、Java中创建线程的方法

(1)创建子类,继承Thread,重写run,调用start

package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 创建并启动MyThread线程MyThread myThread = new MyThread();// myThread.start();myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}

(2)创建子类,实现Runnable,重写run,搭配Thread对象进行start

package Thread;class MyRunnable implements Runnable {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}public class Demo2 {public static void main(String[] args) {Runnable runnable = new MyRunnable();//runnable没有start方法,所以不能直接调用run方法,需要将runnable对象作为参数传递给Thread类的构造方法,然后调用start方法启动线程 Thread thread = new Thread(runnable);thread.start();}
}

使用Runnable描述线程要执行的任务是啥,真正去创建线程的工作,还是由Thread来负责。

interface是接口,不包含具体的方法的实现,只是提供一系列抽象方法,让子类去重写

*接口的的default一般不用,日常开发的时候,用到接口,都是希望全都提供抽象方法。除非是接口提供了一组方法,这一组方法中存在一些“公共的逻辑”,就可以在接口中搞default方法,使得这个方法表示公共逻辑,后面就可以在重写其他抽象方法的时候去调用了。

对于第一种写法(继承Thread)描述任务的时候,任务代码是写到Thread子类中的,意味着任务内容和Thread类的耦合程度更高(*写代码要高内聚/低耦合)

未来如果想把这个任务给别的“主体”去执行(执行一个任务,线程只是其中一个选项,进程也可以,协程亦可以...)

第二种写法,任务是写到Runnable中的,几乎不涉及到任何和“线程”相关的概念,任务内容和Thread概念的耦合是很小的,几乎没有

任务内容和Thread概念的耦合是很小的,几乎没有

后序就可以把这个任务交给进程、协程来执行

(*协程:近几年提出的概念,可以理解为“轻量级线程”)

(3)继承Thread,重写run,调用start 通过匿名内部类

package Thread;public class Demo3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};t.start();}}

此处就是创建了没有名字的匿名内部类,这个类就是Thread的子类,子类重写了run方法,同时也创建了子类的实例,通过t引用指向。

(4)实现Runnable,重写run,搭配Thread调用start 通过匿名内部类

package Thread;public class Demo4 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};Thread t = new Thread(runnable);t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

(5)更简单的写法,基于lambda表达式创建线程

lambda表达式,本质上是“匿名方法”

package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

二、后台线程与前台线程

后台线程:线程没运行完,进程可以结束(线程不能够组织进程结束)

前台线程:线程没有运行完,进程就不会结束(线程能够组织进程结束)

main线程和我们自己创建的线程都是前台线程,isDaemon = false;

剩下的线程就是后台线程,isDaemon = true;(守护线程)

通过setDaemon设置线程为后台(*必须在start之前set)

什么样子的线程是前台线程,什么样子的线程是后台线程呢?

如果一个线程做的任务很重要,这个任务必须要昨晚你,就应该设置这个线程为前台线程

如果一个线程做的任务无关紧要/周期性无期限执行(比如说JVM的垃圾回收线程),就应该设置为后台线程

package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});t.setDaemon(true);t.start();//把t线程设置为守护线程,当main线程结束时,t线程也会结束for(int i = 0;i<3;i++){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

三、判断线程是否存活

package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});System.out.println("t线程是否存活:"+t.isAlive());//false,t线程还没有启动,所以isAlive返回falset.setDaemon(true);t.start();//把t线程设置为守护线程,当main线程结束时,t线程也会结束System.out.println("t线程是否存活:"+t.isAlive());//true,t线程已经启动,所以isAlive返回truefor(int i = 0;i<3;i++){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("t线程是否存活:"+t.isAlive());//false,t线程已经结束,所以isAlive返回false}}

相关文章:

  • 基于SpringBoot+Vue实现的旅游景点预约平台功能一
  • [密码学实战]密码服务平台部署架构详解与学习路线
  • QT+CDB进行debug调试无法查看QString等qt相关变量值问题的修复
  • AI——神经网络以及TensorFlow使用
  • 树莓派超全系列教程文档--(36)树莓派条件过滤器设置
  • 测试新版oda teigha,开发webcad,实现在线查看dwg图纸
  • 人工智能-机器学习其他技术(决策树,异常检测,主成分分析)
  • hadoop分布式部署
  • 基于深度学习的线性预测:创新应用与挑战
  • JSON-RPC远程控制
  • AI与思维模型【69】——人类误判心理
  • 方案精读:数字政府智慧政务服务一网通办服务解决方案【附全文阅读】
  • 【Oracle专栏】删除用户 释放表空间
  • 架构师面试(三十四):IM 假在线
  • Linux 文件系统目录结构详解
  • 什么是 IDE?集成开发环境的功能与优势
  • 基于springboot+vue的仓库管理系统
  • AWS Linux快速指南:5分钟搭建多用户图形界面
  • 使用 Docker Swarm 纳管节点并实现容器通信
  • 【网络】数据链路层知识梳理
  • 河北衡水中学再换校长
  • 中国将召集安理会非正式会议讨论美加征关税问题?外交部回应
  • 习近平会见马来西亚最高元首:开启中马关系新的“黄金50年”
  • 首批进驻6省区!二十届中央第五轮巡视开始进驻