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

java复习 06

线程还没学会,然后查漏补缺。再学一下泛型,下一篇博客写。

1 线程控制

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join()等待这个线程死亡
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java 虚拟机将退出

sleep方法的应用,这里用trycatch包围 

package PTA_training.Thread_training;public class ThreadSleepDemo {public static void main(String[] args) {ThreadSleep s1 = new ThreadSleep();ThreadSleep s2 = new ThreadSleep();ThreadSleep s3 = new ThreadSleep();s1.setName("啊");s2.setName("a");s3.setName("1");s1.start();s2.start();s3.start();}}
package PTA_training.Thread_training;public class ThreadSleep extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() +  ":" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

 join方法使用后,要等这个线程死亡了以后才会执行新的线程

看起来就是现在都是在执行这个(给了优先级,但和优先级的概念定义是不一样的!!!!要是现在忘记setpriority的掉头看以前的博客!!!!!)

Java 中 setDaemon

作用:标记线程为守护线程(Daemon Thread)。JVM 中若所有运行线程都是守护线程,JVM 会退出。比如垃圾回收线程就是守护线程,辅助程序清理内存,不影响程序核心逻辑,程序结束时无需等它手动收尾。

使用规则:必须在线程 start() 启动前调用 ,否则抛 IllegalThreadStateException 异常。

package PTA_training.Thread_training;public class ThreadSleepDemo {public static void main(String[] args) {ThreadSleep s1 = new ThreadSleep();ThreadSleep s2 = new ThreadSleep();s1.setName("啊");s2.setName("a");//设置主线程为1Thread.currentThread().setName("1");//设置守护线程s1.setDaemon(true);s2.setDaemon(true);s1.start();s2.start();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}
}
package PTA_training.Thread_training;public class ThreadSleep extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() +  ":" + i);}}
}

 2 线程的生命周期

线程生命周期描述了线程从创建到销毁的整个过程,Java 线程(以常见的线程模型为例 )生命周期主要包含以下 5 个阶段,对应状态及转换逻辑如下:

1. 新建(New)

  • 状态说明:通过 new Thread() 等方式创建线程对象,但还未调用 start() 方法。此时线程仅在内存中存在,未与操作系统底层线程关联,不具备 “运行潜力”。
  • 典型操作:Thread thread = new Thread(() -> { /* 任务逻辑 */ });

2. 就绪(Runnable)

  • 状态说明:调用 start() 方法后,线程进入就绪态。此时线程已 “激活”,具备执行资格(可参与 CPU 竞争),但没有执行权(需等待 CPU 调度 )。JVM 会把它放到 “就绪队列”,等待操作系统分配 CPU 时间片。
  • 状态转换
    • 从 新建 来:start() 调用后进入就绪。
    • 从 阻塞 / 运行 回:若线程因 sleep 时间到、阻塞解除(如锁释放 )、被其他线程抢走 CPU 执行权等,会回到就绪态,重新等 CPU 调度。

3. 运行(Running)

  • 状态说明:就绪线程抢到 CPU 执行权后,进入运行态,开始执行 run() 里的逻辑。此时线程真正在 “干活”,占据 CPU 资源处理任务。
  • 状态转换
    • 从 就绪 来:CPU 调度选中线程,切换到运行态。
    • 到 就绪 / 阻塞 / 死亡
      • 若 CPU 时间片用完(或被更高优先级线程抢占 ),回到就绪态
      • 若执行 sleep()、等待锁、IO 阻塞等操作,进入阻塞态
      • 若 run() 正常结束或调用 stop()(已被标记为 “过时”,不推荐用,易引发问题 ),进入死亡态

4. 阻塞(Blocked)

  • 状态说明:线程因特定原因暂时失去执行资格(既没执行权,也无法竞争 CPU ),需等 “阻塞条件解除” 才能回归就绪态。
  • 触发场景(图中示例 ):
    • 调用 sleep(long millis):线程休眠指定毫秒,期间不参与 CPU 竞争,时间到后回到就绪态;
    • 其他阻塞操作:比如等待 synchronized 锁、IO 等待(读文件 / 网络请求 )等,需等锁拿到、IO 完成,才会解除阻塞。
  • 状态转换:阻塞条件消失(如 sleep 时间到、锁可用 )后,回到就绪态,重新等 CPU 调度。

5. 死亡(Terminated)

  • 状态说明:线程执行完毕(run() 正常结束 )或被强制终止(如调用 stop(),但不推荐 ),进入死亡态。此时线程生命周期结束,无法再回到其他状态,会被 JVM 回收资源。
  • 触发方式
    • 自然死亡:run() 方法逻辑执行完,线程正常退出;
    • 强制死亡:调用 stop()(风险高,可能导致资源未释放、数据不一致,现代开发更推荐通过标志位等 “优雅终止” 方式 )。

关键逻辑梳理

  • 核心是 “状态流转”:新建→就绪→运行→(就绪 / 阻塞 / 死亡 )→…→死亡。
  • 线程能否执行任务,核心看是否拿到 CPU 执行权(就绪态竞争、运行态干活 );阻塞态是 “被迫暂停”,需等条件满足才能重新竞争。
  • 实际开发中,要注意线程阻塞可能导致的性能问题(如大量线程因锁阻塞 ),以及合理控制线程生命周期(避免滥用 stop(),用标志位让线程 “优雅退出” )。

简单说,线程就像 “打工人”:新建是刚入职还没开工,就绪是排队等 “分配工作(CPU)”,运行是正在干活,阻塞是被迫暂停(等审批 / 等资源 ),死亡是干完活下班~ 理解这几个阶段,就能更好把控多线程程序的执行流程啦 。

3 写编程题的感受......(踩坑实录)

初始代码

package PTA_training.Test4_7;import java.util.Scanner;/*
7-7 创建一个倒数计数线程
创建一个倒数计数线程。
要求:
1.该线程使用实现Runnable接口的写法;
2.程序该线程每隔0.5秒打印输出一次倒数数值(数值为上一次数值减1)。输入格式:
N(键盘输入一个整数)输出格式:
每隔0.5秒打印输出一次剩余数输入样例:
6
输出样例:
在这里给出相应的输出。例如:6
5
4
3
2
1
0*/
public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();Thread t1 = new Thread(new TimeCounter(n));try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}scanner.close();t1.start();}
}
package PTA_training.Test4_7;public class TimeCounter implements Runnable{private  int n;public TimeCounter(int n) {this.n = n ;}@Overridepublic void run() {for (int i = n ; i < 0 ; i--) {System.out.println(i);}}
}

这段代码存在几个问题,下面来详细分析并给出解决方案:

问题分析

  1. for 循环条件错误:在 TimeCounter 类的 run 方法里,for 循环的条件 i < 0 有误。从 n 开始倒数,循环条件应当是 i >= 0 ,这样才能正确输出从 n 到 0 的所有数字。(哈哈...哈哈哈......我好蠢啊......)

  2. 时间间隔设置错误:在 Main 类中,Thread.sleep(500) 被放在了启动线程之前,这就意味着主线程会休眠 0.5 秒,而不是让线程每隔 0.5 秒输出一次。!正确的做法是把 Thread.sleep(500) 放到 TimeCounter 类的 run 方法中。

  3. 异常处理问题:在 Main 类里,Thread.sleep(500) 抛出的 InterruptedException 被重新抛出为 RuntimeException ,这会导致程序崩溃。应该在 TimeCounter 类的 run 方法里处理 InterruptedException 。

 最终代码:

package PTA_training.Test4_7;public class TimeCounter implements Runnable {private int n;public TimeCounter(int n) {this.n = n;}@Overridepublic void run() {for (int i = n; i >= 0; i--) {System.out.println(i);try {// 线程休眠 0.5 秒Thread.sleep(500); } catch (InterruptedException e) {// 处理中断异常Thread.currentThread().interrupt(); }}}
}
package PTA_training.Test4_7;import java.util.Scanner;/*
7-7 创建一个倒数计数线程
创建一个倒数计数线程。
要求:
1.该线程使用实现Runnable接口的写法;
2.程序该线程每隔0.5秒打印输出一次倒数数值(数值为上一次数值减1)。输入格式:
N(键盘输入一个整数)输出格式:
每隔0.5秒打印输出一次剩余数输入样例:
6
输出样例:
在这里给出相应的输出。例如:6
5
4
3
2
1
0*/
public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();Thread t1 = new Thread(new TimeCounter(n));scanner.close();t1.start();}
}

4 Java 多线程实现的两种方式

Java 提供了两种主要方式来实现多线程编程:通过继承 Thread 类和通过实现 Runnable 接口。这两种方式各有特点,下面我将详细介绍它们的实现方法和区别。

1. 通过继承 Thread 类实现多线程

这是实现多线程的最直接方式,通过创建一个继承自 Thread 类的子类,并重写其 run () 方法:

public class RunnableWithThreadMethods {public static void main(String[] args) {// 创建Runnable任务MyTask task = new MyTask();// 创建多个线程共享同一个任务Thread thread1 = new Thread(task);Thread thread2 = new Thread(task);// 设置线程名称thread1.setName("工作线程-1");thread2.setName("工作线程-2");// 设置线程优先级thread1.setPriority(Thread.MAX_PRIORITY);thread2.setPriority(Thread.MIN_PRIORITY);// 启动线程thread1.start();thread2.start();}
}class MyTask implements Runnable {@Overridepublic void run() {// 获取当前执行该任务的线程Thread currentThread = Thread.currentThread();// 使用线程的各种方法System.out.println("线程名称: " + currentThread.getName());System.out.println("线程优先级: " + currentThread.getPriority());System.out.println("线程ID: " + currentThread.getId());System.out.println("线程状态: " + currentThread.getState());System.out.println("是否为守护线程: " + currentThread.isDaemon());// 在线程中调用sleep方法try {System.out.println(currentThread.getName() + " 开始休眠");Thread.sleep(2000); // 等同于 currentThread.sleep(2000)System.out.println(currentThread.getName() + " 休眠结束");} catch (InterruptedException e) {System.out.println(currentThread.getName() + " 被中断");}}
}

这种方式的核心是继承 Thread 类并重写 run () 方法,然后通过调用 start () 方法启动线程。需要注意的是,直接调用 run () 方法不会创建新线程,只是在当前线程中执行方法体。

2. 通过实现 Runnable 接口实现多线程

这是更常用的实现多线程的方式,通过创建一个实现 Runnable 接口的类,然后将其作为参数传递给 Thread 类的构造函数:

public class RunnableExample {public static void main(String[] args) {// 创建Runnable实例MyRunnable runnable1 = new MyRunnable("任务1");MyRunnable runnable2 = new MyRunnable("任务2");// 创建线程实例,将Runnable对象作为参数传入Thread thread1 = new Thread(runnable1);Thread thread2 = new Thread(runnable2);// 启动线程thread1.start();thread2.start();}
}// 实现Runnable接口创建线程任务
class MyRunnable implements Runnable {private String name;public MyRunnable(String name) {this.name = name;}// 实现run()方法,定义线程执行的任务@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(name + " 运行: " + i);try {// 线程休眠,模拟耗时操作Thread.sleep((int)(Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}}}
}

这种方式的优点是实现了任务与线程的分离,Runnable 对象代表要执行的任务,而 Thread 对象负责线程的创建和管理。

两种方式的对比

特性继承 Thread 类实现 Runnable 接口
编程复杂度简单,直接继承并重写 run () 方法稍复杂,需要创建 Runnable 对象并传递给 Thread
资源占用每个线程都需要创建一个独立的 Thread 对象,资源占用较多多个线程可以共享同一个 Runnable 对象,资源占用较少
扩展性由于 Java 不支持多重继承,继承 Thread 后不能再继承其他类可以继承其他类并实现 Runnable 接口,扩展性更好
代码复用性低,每个线程类只能完成特定任务高,同一个 Runnable 可以被多个线程共享
适用场景简单任务,不需要共享资源的场景复杂任务,需要共享资源的场景

推荐使用方式

在实际开发中,更推荐使用实现 Runnable 接口的方式,原因如下:

  1. 避免了 Java 单继承的限制,类可以同时继承其他类并实现多线程
  2. 更好地实现了任务与线程的分离,提高了代码的可维护性和复用性
  3. 更适合需要共享资源的场景,多个线程可以共享同一个 Runnable 实例
  4. 符合面向对象的设计原则,任务逻辑和线程管理职责分明

无论选择哪种方式,都要记住:线程的启动必须通过调用 start () 方法,而不是直接调用 run () 方法。

5  如果在runnable接口里面想要实现thread的方法setName应该怎么做?这种做法又叫什么?

在 Runnable 接口中使用 Thread 的 setName 方法

在 Java 中,Runnable 接口本身并没有提供setName()方法,因为它只定义了一个run()方法。要在使用 Runnable 接口的同时设置线程名称,你需要通过 Thread 对象来实现。

这实际上是一种 ** 组合(Composition)** 设计模式的应用。组合是一种比继承更灵活的代码复用方式,它通过将一个类的实例作为另一个类的成员变量来实现功能复用,而不是通过继承。

下面是如何在使用 Runnable 接口时设置线程名称的示例:

public class RunnableWithName {public static void main(String[] args) {// 创建Runnable任务MyRunnable task = new MyRunnable();// 创建Thread并设置名称!!!!!!!!!!Thread thread1 = new Thread(task, "线程A");Thread thread2 = new Thread(task, "线程B");// 或者通过setName方法设置Thread thread3 = new Thread(task);thread3.setName("线程C");// 启动线程thread1.start();thread2.start();thread3.start();}
}// 实现Runnable接口的任务类
class MyRunnable implements Runnable {@Overridepublic void run() {// 获取当前线程名称String threadName = Thread.currentThread().getName();for (int i = 0; i < 5; i++) {System.out.println(threadName + " 运行: " + i);try {Thread.sleep((int)(Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}}}
}

组合与继承的对比

这种做法体现了 "组合优于继承" 的设计原则:

  1. 组合(Composition)

    • 通过将 Thread 对象作为载体,将 Runnable 任务组合到 Thread 中
    • 可以在运行时动态改变线程的行为
    • 避免了继承的限制(Java 单继承)
    • 更符合单一职责原则,Runnable 专注于定义任务,Thread 负责线程管理
  2. 继承(Inheritance)

    • 通过继承 Thread 类直接获取其所有方法
    • 关系更紧密,但灵活性较差
    • 可能导致类层次结构复杂
    • 受到 Java 单继承的限制

实际应用

在实际开发中,组合方式更为常用,特别是在需要共享资源的场景中:

  • 多个线程可以共享同一个 Runnable 实例
  • 可以在不修改 Runnable 代码的情况下,为线程添加额外功能(如设置优先级、守护线程等)
  • 符合面向对象设计中的依赖倒置原则,高层模块(线程)不依赖于低层模块(具体实现)的细节

通过这种方式,你可以在保持代码灵活性和可维护性的同时,充分利用 Thread 类提供的各种功能。

6 意思就是Thread.currentThread()然后点调用对吧?!是的 !

是的,你理解得完全正确!

在 Runnable 接口的实现中,你可以通过Thread.currentThread()获取当前执行该 Runnable 的线程对象,然后调用线程的所有方法,包括setName()sleep()interrupt()等。

这种方式的核心在于:Runnable 接口定义了要执行的任务,而 Thread 类负责线程的创建和管理。当你在 Runnable 的run()方法中使用Thread.currentThread()时,你实际上是在访问执行该任务的线程对象。

下面是一个更清晰的示例,展示如何在 Runnable 中使用 Thread 的各种方法:

这种方式的优势

  1. 解耦设计:将任务逻辑与线程管理分离,使代码更清晰
  2. 资源共享:多个线程可以共享同一个 Runnable 实例,实现资源共享
  3. 灵活配置:可以为每个线程单独配置属性(名称、优先级、守护状态等)
  4. 扩展性强:可以轻松实现更复杂的线程池、并发控制等功能

关于 "这种做法叫什么"

这种做法在设计模式中被称为组合模式(Composition Pattern),它是 "组合优于继承" 设计原则的具体应用。通过组合,你可以在不继承 Thread 类的情况下,利用 Thread 类提供的功能。

另外,从更广泛的角度看,这也是 ** 依赖注入(Dependency Injection)** 的一种形式,因为 Runnable 任务依赖于 Thread 对象来执行。

在实际开发中,这种方式是实现多线程的首选方法,特别是在需要共享资源或实现复杂并发逻辑的场景中。

7 小结一下多线程的实现方式


方式 2:实现 Runnable 接口

  • 定义一个类 MyRunnable 实现 Runnable 接口
  • 在 MyRunnable 类中重写 run () 方法
  • 创建 MyRunnable 类的对象
  • 创建 Thread 类的对象,把 MyRunnable 对象作为构造方法的参数
  • 启动线程

多线程的实现方案有两种

  • 继承 Thread 类
  • 实现 Runnable 接口

相比继承 Thread 类,实现 Runnable 接口的好处

  • 避免了 Java 单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

相关文章:

  • shell脚本质数判断
  • 可下载旧版app屏蔽更新的app市场
  • Cursor+MCP编程工具助力开发效率倍增
  • React Native 弹窗组件优化实战:解决 Modal 闪烁与动画卡顿问题
  • python数据结构和算法(1)
  • 2025-06-09 java面试总结
  • 【Elasticsearch基础】Elasticsearch批量操作(Bulk API)深度解析与实践指南
  • pymilvus
  • 保险风险预测数据集insurance.csv
  • Java UDP网络通信实战指南
  • Shell脚本流程控制:从基础语法到实战应用
  • 统计按位或能得到最大值的子集数目
  • vs code无法ssh远程连接linux机器----解决方案
  • tomcat指定使用的jdk版本
  • 【VLNs篇】07:NavRL—在动态环境中学习安全飞行
  • 基于Android 开发完成的购物商城App--前后端分离项目
  • WDK 10.0.19041.685,可在32位win7 sp1系统下搭配vs2019使用,可以编译出xp驱动。
  • 【使用LLM搭建系统】5 处理输入: 链式 Prompt Chaining Prompts
  • 深入探讨渗透测试的定义、关键步骤以及实施方法
  • vue3+vite+pnpm项目 使用monaco-editor常见问题
  • 哪个网站有高清图片做ppt/百度seo排名优化公司
  • 汕头招聘网官网登录/广州灰色优化网络公司
  • 具有价值的做pc端网站/杭州网站
  • 郑州直销网站制作/开通网站需要多少钱
  • 动态网站 教程/百度学术官网入口网页版
  • 电商seo推广/seo品牌优化