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

【Java】多线程Thread类

1. 进程与线程

进程与线程的基本认识

进程(Process):进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。

线程(Thread):线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

即使多进程可以提高硬件资源的利用率,但是进程的启动和销毁需要消耗大量的系统性能,从而导致程序的执行性能下降。所以为了进一步提升并发操作的处理能力,在进程的基础上又划分了多线程概念。

2. 多线程实现

Thread类实现多线程

 Thread类的方法

1.通过继承Thread类,并重写父类的run()方法实现
public void run(){ }

定义线程类:


class MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){throw new RuntimeException(e);}}}
}
public class Demo1 {public static void main(String[] args)throws InterruptedException {Thread t=new MyThread();t.start();//创建一个线程//t.run();while(true){System.out.println("hello main");Thread.sleep(1000);}//两个循环在同时进行}
}

Runnable接口实现多线程

使用Thread类的确可以实现多线程,但是也容易发现它的缺陷:面向对象的单继承局限,因此才采用Runnable接口来实现多线程。

该接口的定义如下(以下并不需要在代码中实现):


class MyRunnable implements Runnable{@Overridepublic void run(){while(true){System.out.println("hello main");try{Thread.sleep(1000);}catch(InterruptedException e){throw new RuntimeException(e);}}}
}
public class Demo2 {public static void main(String[] args)throws Exception{Runnable runnable=new MyRunnable();Thread t=new Thread(runnable);t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

Thread(String name)后边两种方法,我们可以给线程对象命名。

默认情况下,线程对象名字是Thread-N,N是从0开始增加。

3.Thread常见方法

start()

start() 启动一个线程。

start()方法可以开启一个线程,线程创建好后是不会开始执行的,要使用start来开启。

只有当start(),操作系统内核才会创建一个真真正正的线程PCB,然后去调用run()方法,此时操作系统层面才会真的创建一个线程。

sleep()

使用sleep时会有一个中断异常(非受查异常),需要我们手动抛出

join()

join(): 等待一个线程;

join(t)表示等待一个线程t毫秒。
因为我们线程是随即调度的过程,线程的执行顺便我们无法把控,但在一些场景下我们需要有明确的线程程序顺序,等待线程,就是可以控制两个线程的结束顺序。

比如在main方法中有两个线程t1、t2,在主线程中有t1.join() ,那么主线程就要阻塞 等待t1线程执行完了才能在执行。如果在t1线程中有t2.join(),那么t1线程就要阻塞 等待t2线程执行完了才能执行任务。

如果去掉join()方法后,主线程与Thread线程抢占式执行!


public class Demo12 {public static void main(String[] args)throws Exception {Thread t=new Thread(()->{for(int i=0;i<3000;i++){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程结束");});t.start();t.join(3000);//等待线程t执行完毕System.out.println("main 线程结束");}
}

中断线程

线程中断: 我们这里的中断,并不是线程立即就结束了,而是通知该线程,你应该结束了,但实际是否结束,取决于线程里具体的代码

package Thread;public class Demo3 {public static  boolean flag=true;public static void main(String[] args)throws InterruptedException{Thread t1=new Thread(()->{while(flag){System.out.println("hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}}});t1.start();Thread.sleep(3000);flag=false;}
}

flag 变量的作用是作为线程执行的控制开关,用于安全地终止 t1 线程的循环执行。

flag 变量是主线程与 t1 线程之间的一个通信媒介,通过修改它的值,主线程可以安全地通知 t1 线程“该停止工作了”,避免了线程的无限循环。如果没有 flagt1 线程的 while(true) 循环将无法终止,导致线程永久运行

即使 t1 线程因 sleep 进入阻塞状态,只要 sleep 时间结束,线程恢复运行后会立即检查 flag,从而响应终止信号。

4.Thread类的常见属性

getId():

 ID是线程的唯一标识,不同的线程不重复

package Thread;class MyThread1 extends Thread{@Overridepublic void run(){System.out.println("hello word");}
}
public class getIdtest {public static void main(String[] args) {Thread t=new MyThread1();System.out.println(t.getId());}
}

getName: 

名称是我们构造方法设置的名称,如果没有设置,那就是默认名。

public class getNametest {public static void main(String[] args) {Thread t=new Thread();System.out.println(t.getName());//因为我们没有命名,所以使用的是默认名。}
}

getState(): 

状态:

getState()方法可以获取当前Thread对象的状态,这是Java层面定义的线程状态,要注意和PCB上的区分。

线程有6种状态:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING,  TERMINATED。

NEW表示创建好了一个Java线程对象,安排好了任务,只是还没有启动,没有调用start方法之前是不会创建PCB的,和PCB没有关系。

RUNNABLE是运行状态或就绪状态,在操作系统层面已经有了对应的PCB。

BLOCKED是阻塞状态的一种,在等待锁

WAITING表示一直没有时间限制的等待,一直等直到被唤醒。

TIMED_WAITING指定了等待时间的阻塞状态,过时就继续执行任务。

TERMINATED是执行结束,PCB已经销毁,Java层面的线程对象还在。

查看Thread对象的所有状态:

package Thread;public class Demo14 {public static void main(String[] args) {for(Thread.State state: Thread.State.values()){System.out.println(state);}}
}

getPriority: 

可以获取当前线程的优先级,但没有太大的用,实际还得取决于操作系统内核的具体调度。

package Thread;public class test {public static void main(String[] args) {Thread t=new Thread();System.out.println(t.getPriority());}
}

isDaemon(): 

是否为守护线程(也叫后台线程), 简单的理解一下,我们手动创建的线程都是前台线程,JVM自带的线程都是后台线程,我们也可以通过setDaemon()方法将前台线程设置为后台线程。

 public static void main(String[] args) {Thread t=new MyThread2();System.out.println(t.isDaemon());}

isAlive(): 

线程的run方法是否执行结束。

package Thread;
class MyThread2 extends Thread{@Overridepublic void run(){System.out.println("hello thread1");}
}
public class Demo4 {public static void main(String[] args) {Thread t=new MyThread2();System.out.println(t.isAlive());t.start();System.out.println(t.isAlive());}}

interrupt()方法

第一种解释:

  1. 发送信号:通知线程“需要中断当前操作”,而非强制杀死线程。
  2. 打破阻塞:中断 sleep/wait 等阻塞方法,让线程从阻塞中唤醒并处理中断。
  3. 协作终止:线程通过检查中断状态(isInterrupted())或处理 InterruptedException,决定是否结束,体现“谁运行谁控制”的设计原则

在 Demo3 中,正是由于 t1 线程内部通过 while (!isInterrupted()) 主动响应中断信号,主线程的 t1.interrupt() 才能安全、优雅地终止线程。

 public static void main(String[] args)throws InterruptedException{Thread t1=new Thread(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}}});t1.start();Thread.sleep(3000);t1.interrupt();}

第二个解释:

1.把线程内部的标志位设置为true
2.如果线程正在sleep,会触发异常,将sleep唤醒。
sleep唤醒的同时,会清空标志位,将刚才设置的标志位在设置为false(这就导致,我们的循环会继续执行).
如果我们想去中断,我们可以在异常里加一个break。

2.3 多线程运行状态

任意线程具有5种基本的状态:

1.创建状态

实现Runnable接口和继承Thread类创建一个线程对象后,该线程对象处于创建状态,此时它已经有了内存空间和其它资源,但他还是处于不可运行的。

2.就绪状态

新建线程对象后,调用该线程的start方法启动该线程。启动后,进入线程队列排队,由CPU调度服务。

3.运行状态

就绪状态的线程获得处理器的资源时,线程就进入了运行状态,此时将自动调用run方法。

4.阻塞状态

正在运行的线程在某些特殊情况下,如:当前线程调用sleep、suspend、wait等方法时,运行在当前线程里的其它线程调用join方法时,以及等待用户输入的时候。只有当引起阻塞原因消失后,线程才能进入就绪状态。

5.终止状态

当线程run方法运行结束后,或者主线程的main()方法结束后,线程才能处于终止状态,线程一旦死亡就不能复生。

http://www.lryc.cn/news/623869.html

相关文章:

  • 2025年- H97-Lc205--23.合并k个升序链表(链表、小根堆、优先队列)--Java版
  • 【撸靶笔记】第二关:GET -Error based -Intiger based
  • 【102页PPT】新一代数字化转型信息化总体规划方案(附下载方式)
  • 2.4 双向链表
  • 牛客周赛 Round 104(小红的矩阵不动点/小红的不动点权值)
  • 03高级语言逻辑结构到汇编语言之逻辑结构转换if (...) {...} else if {...} else {...}
  • react 错误边界
  • git stash临时保存工作区
  • Win11 文件资源管理器预览窗格显示 XAML 文件内容教程
  • 【牛客刷题】成绩统计与发短信问题详解
  • 【Git系列】如何从 Git 中删除 .idea 目录
  • 【线程安全(二) Java EE】
  • 寻找数组的中心索引
  • 如果用ApiFox调用Kubernetes API,需要怎么设置证书?
  • Day16 多任务(2)
  • USB-A 3.2 和 USB-A 2.0的区别
  • Day27 装饰器
  • 从零配置YOLOv8环境:RTX 3060显卡完整指南
  • AI评测的科学之道:当Benchmark遇上统计学
  • 48.Seata认识、部署TC服务、微服务集成
  • [Responsive theme color] 动态更新 | CSS变量+JS操控 | 移动端-汉堡菜单 | 实现平滑滚动
  • 实现用户输入打断大模型流式输出:基于Vue与FastAPI的方案
  • GaussDB 数据库架构师修炼(十三)安全管理(5)-全密态数据库
  • 【每日一题】Day 6
  • 凸函数与损失函数
  • 开源数据发现平台:Amundsen Search Service 搜索服务
  • Python注解
  • 零墨云A4mini打印机设置电脑通过局域网络进行打印
  • C#对象的本地保存与序列化详解笔记
  • GitLab CI/CD、Jenkins与GitHub Actions在Kubernetes环境中的方案对比分析