【多线程编程】创建线程的几种方式 面试题

news/2025/5/16 9:45:13/

创建线程的几种方法

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法 。
  3. Thread+匿名内部类,重写run方法。
  4. Runnable+匿名内部类,重写run方法。
  5. Thread+lambda表达式,不用重写。

 

1.继承Thread类,重写run方法。

//创建一个线程  并发编程   (默认)并发 = 并行 + 并发
class MyThread extends Thread {@Overridepublic void run() {      //方法重写  线程的入口方法while(true) {System.out.println("========");try {//使用静态方法sleep 受查异常 需要抛异常 抛异常有两种 throws 和 try..catch//由于这里是重写方法 重写的方法方法名等都要相同 被重写的方法没有throws 故不能用throws  只能用 try..catchThread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Demo1 {//main 也是一个线程 由JVM自动创建public static void main1(String[] args) {Thread thread = new MyThread();//start 和 run 都是 Thread 的成员//run 只是描述了线程的入口 线程要做什么//start 是调用了系统API,在系统中创建线程,让线程再调用 runthread.start();//thread.run();  //这样调用会先执行调用run方法的线程,此线程执行完后,再执行其他线程}public static void main(String[] args) throws InterruptedException {Thread thread = new MyThread();thread.start();while(true) {System.out.println("********");Thread.sleep(1000);}}}

 

  2. 实现Runnable接口,重写run方法。

class MtThread implements Runnable {  //实现Runnable接口@Overridepublic void run() {while (true) {System.out.println("&&&&&&&&&&&&&&");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Demo02 {public static void main(String[] args) throws InterruptedException {//使用 Runnable 的写法 和 直接继承 Thread 之间的区别主要就是【解耦合】Runnable runnable = new MtThread();     Thread thread = new Thread(runnable);thread.start();while (true) {System.out.println("=========");thread.sleep(1000);}}}

使用实现 Runnable 的写法 和 直接继承 Thread 之间的区别?

      主要区别就是解耦合,解耦合,即就是 实现Runnable 会把任务存在runnable的run方法中,如果要执行此任务,再创建线程进行调用,这样,任务就不会单独属于某个进程,而是创建的进程都可以去调用。实现了任务与线程的分离,即实现Runnable的写法会解耦合。

3.Thread+匿名内部类,重写run方法。

//利用匿名内部类
public class Demo03 {public static void main(String[] args) {//匿名内部类Thread thread = new Thread() {@Overridepublic void run() {while (true) {System.out.println("=======");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};//启动线程thread.start();while (true) {System.out.println("++++++++++++");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

4.Runnable+匿名内部类,重写run方法。

//利用匿名内部类
public class Demo04 {public static void main1(String[] args) {//第一种Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("======");}}};Thread thread = new Thread();thread.start();while (true) {System.out.println("*********");}}public static void main2(String[] args) {//第二种Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("???????");}}});thread.start();}
}

5.Thread+lambda表达式,不用重写。

 public static void main(String[] args) {Thread thread = new Thread( () -> {while (true) {System.out.println("=========");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"这是新线程");    //给线程起名字//设置 thread 为后台进程 改为后台进程后 主线程飞快的执行完了,thread 还没来得及执行整个 进程就完了//前台进程 会影响进程结束  而后台进程则不会即当前台进程若是早于后台进程结束,无论后台结 束与否,整个进程都得结束 //thread.setDaemon(true); //设置 thread 为后台进程thread.start();   //启动线程System.out.println(thread.isAlive());  //线程是否存活}

前台进程 后台进程概念

前台进程 会影响进程结束  而后台进程则不会即当前台进程若是早于后台进程结束,无论后台结 
束与否,整个进程都得结束。

在java中可通过调用方法 setDaemon(true) 来将线程设置为后台进程。

关于后台进程的要点:JVM会在一个进程的所有非后台进程都执行结束完后,才会结束运行。即后台进程完不完并不关心。只关心所有非后台进程完没完。

Thread的几个常见属性

属性获取方法说明
ID

getId()

线程的唯一标识 是Java分配给线程的,不是系统 API 提供的。

名称

getName()线程名
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()简单理解为run方法是否运行结束
是否被中断isInterrupted()标志位

面试题:在JAVA中创建线程都有哪些方式?

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法 。
  3. Thread+匿名内部类,重写run方法。
  4. Runnable+匿名内部类,重写run方法。
  5. Thread+lambda表达式,不用重写。

lamda表达式不用重写的原因及什么是变量捕获?

lambda本质上是一个匿名函数,并且使用lambda表达式的前提是接口为函数式接口,何为函数式接口,即接口中只包含唯一一个抽象方法

而Runnable接口就是函数式接口,只包含一个抽象方法run(),圆括号对应run()方法的参数列表,为空则就为空。箭头后面的一对花括号对应run()方法的方法体。

这就是与匿名内部类的区别:匿名内部类可用于接口中有多个抽象方法的情况

面试题:start方法和run方法的区别?

  •  start方法是启动线程的,在方法内部,会调用系统API,并在系统内核中创建出线程。
  • run方法是线程的入口方法,即告诉线程你要干啥,要执行啥内容。
  • 本质区别是是否在系统内部创建出新的线程。

 

标志位

  • 额外用一个成员变量设置
private  static boolean isQuit = false;   //标志位public static void main1(String[] args) throws InterruptedException {Thread thread = new Thread( () -> {   //lambda 表达式是不需要写 run 方法的 , 自身本就是 runwhile(!isQuit) {System.out.println("线程正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程运行结束");});thread.start();Thread.sleep(5000);isQuit = true;}

若将成员变量设置为局部变量时的问题 

 

 其实将红色方框的代码去掉就好了,但是我们设置标志位的目的就是让 isQuit 在线程执行一段时间后从 false 改为 true 。从而使线程结束,但如果去掉变量就没用了,达不到目的了。返回到问题,即为什么用局部变量就不行呢?原因是与lambda表达式的变量捕获有关

所谓变量捕获,匿名内部类有,lambda也有,即就是访问所在方法或代码块中的局部变量,这个过程被称为"变量捕获"。并且这个局部变量得是有效的最终变量。有效的最终变量不一定是非得用 final 修饰,而是指一个在生命周期中没有被修改过的值。而在上面代码中局部变量 isQuit 在红框框中被修改的话就不符合有效的最终变量。

匿名内部类的变量捕获与lambda表达式的变量捕获的区别?

lambda表达式可以捕获外面的this,而匿名内部类无法直接捕获外面的this。

上述标志位不够好。
因为需要手动创建变量。
并且当线程内部在sleep的时候,主线程修改变量,新线程内部不能及时响应。

下面用另一种方法:Thread类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束。

  • 用Thread的属性方法interrupt()进行标志
public static void main(String[] args) throws InterruptedException {Thread thread = new Thread( () -> {//Thread类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束//isInterrupted() 判断标志位while (!Thread.currentThread().isInterrupted()) {   System.out.println("==========");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();// 1. 若不加break 程序抛出异常后 线程仍然继续正常执行// 2. 加上break ,程序抛出异常后线程立即结束// 3. 做一些其他工作收尾工作 完成之后再break// 其他工作的代码放到这里break;}}});//启动线程thread.start();//5s后线程往下执行Thread.sleep(5000);//设置标志位 即让线程中断停止thread.interrupt(); }

代码中thread.interrupt();这个操作,就是把Thread对象内部的标志位设置为true了。即使线程内部还在sleep,也是可以通过这个方法立即唤醒的。这就是使用这个方法比额外使用一个成员变量充当标志位的优点,及时。并且会抛出InterruptedException异常。

有个注意点是; 一定要在 try... catch 的 catch 里加 break;

如果不加break,程序抛出异常后线程仍然继续正常执行。原因是interrupt()唤醒线程之后,sleep()方法抛出异常,同时会自动清除刚才设置的标志位。while() 循环又再次可以执行。

加了break之后,程序抛出异常后线程立即结束。

 

线程等待 join

无参join()  谁调用,等待谁,直至结束为止 

带参join(1000)  单位是毫秒,等待一秒,但这里会有一个调度开销,等待完成后只是立即进入就绪状态,并不一定立即就执行。

进程,线程的状态

对进程来说,最核心的状态就是就绪状态和阻塞状态,对线程也是。

而在Java中,还赋予了线程一些其他的状态。

状态说明
NEWThread对象已经创建但start方法还没调用时
TERMINATEDThread对象还在,内核中的线程不存在了
RUNNABLE就绪状态(线程已经上CPU执行 / 线程正等待上CPU)
TIMED_WAITING阻塞:由于sleep这种固定时间的方式产生的阻塞
WAITING阻塞:由于wait这种不固定时间的方式产生的阻塞
BLOCKED阻塞:由于锁竞争导致的阻塞

通过这些方法,若在编程中遇到线程卡死等问题时就可以通过这些状态来确定原因。


http://www.ppmy.cn/news/1136657.html

相关文章

OpenGLES:绘制一个混色旋转的3D圆锥

效果展示: 本篇博文总共会实现两种混色旋转的3D圆锥: 一.圆锥解析 1.1 对圆锥的拆解 上一篇博文讲解了绘制圆柱体,这一篇讲解绘制一个彩色旋转的圆锥 在绘制圆柱体时提到过,关键点是先将圆柱进行拆解,便于创建出顶…

【Linux】Linux常用命令—文件管理(上)

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

stm32之智能小车总结

作为学习stm32f103c8t6阶段的收官项目&#xff0c;这里做下总结&#xff0c;源码放在了最后。 一、功能描述 1、跟随功能2、循迹功能3、避障功能4、测速功能5、温湿度常显6、oled显示7、语音或蓝牙进行功能切换8、手势功能 二、主要代码解析 2.1、main main函数主要展示whi…

提高应用程序测试覆盖率的 4 个步骤

什么是测试覆盖率以及为什么它很重要&#xff1f; 简而言之&#xff0c;测试覆盖率衡量您测试了多少应用程序。这不仅仅与您执行了多少测试有关。它还与您查看的真实设备、浏览器和操作系统版本有关&#xff01; 您测试的可能设备和操作系统组合越多&#xff0c;测试覆盖的代…

路径问题【动态规划】

一、不同路径 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m1,vector<int>(n1));dp[0][1] 1;for(int i 1;i < m;i){for(int j 1;j < n;j){dp[i][j] dp[i-1][j]dp[i][j-1];}}return dp[m][n];} }; 二、不同路…

(ubuntu)Docker 安装linux 详情过程

文章目录 前言Docker 安装linux第一步&#xff1a;使用dokcker 拉取镜像&#xff1a;第二步&#xff1a;创建本地目录&#xff08;用于挂载&#xff09;第三步&#xff1a;&#xff08;上传配置文件&#xff09;修改配置文件第四步&#xff1a;创建docker容器第五步: 测试本地连…

文举论金:黄金原油全面走势分析策略独家指导

市场没有绝对&#xff0c;涨跌没有定势&#xff0c;所以&#xff0c;对市场行情的涨跌平衡判断就是你的制胜法宝。欲望&#xff01;有句意大利谚语&#xff1a;让金钱成为我们忠心耿耿的仆人&#xff0c;否则&#xff0c;它就会成为一个专横跋扈的主人。空头&#xff0c;多头都…

【Typescript】面向对象(上篇),包含类,构造函数,继承,super,抽象类

假期第七篇&#xff0c;对于基础的知识点&#xff0c;我感觉自己还是很薄弱的。 趁着假期&#xff0c;再去复习一遍 面向对象&#xff1a;程序中所有的操作都需要通过对象来完成 计算机程序的本质就是对现实事物的抽象&#xff0c;抽象的反义词是具体。比如照片是对一个具体的…