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

【JavaEE初阶】 多线程编程核心:解锁线程创建、方法与状态的创新实践密码

在这里插入图片描述

我的个人主页
我的专栏人工智能领域、java-数据结构、Javase、C语言,MySQL,JavaEE初阶,希望能帮助到大家!!! 点赞👍收藏❤

在这里插入图片描述
在这里插入图片描述

文章目录

  • 一:初识线程(Thread)
    • 1.1:线程的概念
    • 1.2:为什么需要线程?
    • 1.3:那么进程和线程的区别是什么?
    • 1.4:Java 的线程和操作系统线程的关系
    • 二:多线程程序的编写
      • 2.1:继承 Thread 类
      • 2.2:实现 Runnable 接口
      • 2.3:匿名内部类创建 Thread 子类对象
      • 2.4:匿名内部类创建 Runnable 子类对象
      • 2.5:lambda 表达式创建 Runnable 子类对象
      • 三:在多线程下-增加了运行速度
        • 四:Thread类及常见的方法
          • 4.1:Thread常见的构造方法
          • 4.2:Thread几个常见的属性
          • 4.3:启动一个线程 - start()
          • 4.4:中断一个线程
          • 4.5:线程等待-join()
          • 五:线程的状态
            • 5.1:线程的状态是一个枚举类型 Thread.State

一:初识线程(Thread)

1.1:线程的概念

   一个线程就是一条 “执行流”。每个线程各自按顺序执行自己的代码,多个线程之间则 “并发” 执行多份代码。线程(Thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

举一个例子:厨房做菜
想象一个繁忙的厨房正在准备顾客丰盛的晚餐,这个厨房就是一个 “进程”。

进程:一个正在执行的程序实例。它拥有独立的资源,如内存空间、文件句柄等。就像这个厨房,它拥有自己的空间、厨具、食材等资源。

线程:进程内部的一个独立执行流。一个进程可以包含多个线程,它们共享进程的资源。就像厨房里的厨师,每位厨师都是一个独立的“线程”。

这里就可以分为:

单线程厨房,即一个厨师完成所有的做菜流程

多线程厨房:即多位厨师分工合作,每个厨师分工完成各自的任务。

1.2:为什么需要线程?

首先,“并发编程”已成为“刚需”。

  • 单核CPU的发展遭遇瓶颈,要提升算力就需依赖多核CPU,而并发编程能更充分地利用多核CPU资源。
  • 部分任务场景存在“等待IO”的情况,为了在等待IO的时间里处理其他工作,也需要用到并发编程。CPU可以切换到另一个线程去执行,避免了CPU时间的浪费。就像一位厨师在等待水烧开时,可以先去切菜,而不是空等。

其次,尽管多进程也能实现并发编程,但线程比进程更轻量:

  • 创建线程比创建进程更快;
  • 销毁线程比销毁进程更快;
  • 调度线程比调度进程更高效。

但是,即便线程已比进程轻量,人们仍不满足,进而发展出了“线程池”(ThreadPool)“协程”(Coroutine)

1.3:那么进程和线程的区别是什么?

  • 进程包含线程,每个进程至少存在一个线程,即主线程。

  • 进程之间不共享内存空间,而同一进程内的线程共享该进程的内存空间。

  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。

  • 一个进程崩溃通常不会影响其他进程,但一个线程崩溃可能导致整个进程(及其中所有线程)一同崩溃。

1.4:Java 的线程和操作系统线程的关系

线程是操作系统层面的概念,其机制由操作系统内核实现,并向用户层提供API供调用(例如Linux的pthread库)。

Java标准库中的Thread类,可视为对操作系统提供的线程API进行了进一步的抽象与封装。

二:多线程程序的编写

2.1:继承 Thread 类

package ThreadDemo;
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 thread=new MyThread();thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

在这里插入图片描述
可以看到hello threadhello main都打印出来了,但是不是交替出现的。这里就体现了操作系统调度线程是随机的.
每个线程都是独立的工作流,多个线程之间是并发执行的
当我们这里将主线程换成thread.run(),因为上面的是死循环只看到了打印hello thread
在这里插入图片描述

在这里我们可以利用Java中的jconsole 命令观察线程

在这里插入图片描述

右键点击以管理员身份运行,选择我们正在运行的线程,点击连接即可
在这里插入图片描述
在这里插入图片描述
其中main就是我们的主线程,Thread-0就是我们通过MyThread类创建的线程,其他的线程都是我们jvm自带的,来负责对线程进行一些背后的操作。

点击main可以看到详细信息
在这里插入图片描述
这里是使用thread.start()情况下当我们使用thread.run()就没有Thread-0线程了。
在这里插入图片描述

2.2:实现 Runnable 接口

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

在这里插入图片描述

注意:
runnable 没有 start 方法.
要想启动线程, 需要搭配 Thread. runnable.start();

2.3:匿名内部类创建 Thread 子类对象

package ThreadDemo;
public class demo3 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(){@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

2.4:匿名内部类创建 Runnable 子类对象

package ThreadDemo;
public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new Runnable(){public void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread t=new Thread(runnable);t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

2.5:lambda 表达式创建 Runnable 子类对象

package ThreadDemo;
public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new Runnable(){public void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread t=new Thread(runnable);t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

三:在多线程下-增加了运行速度

使用 System.nanoTime() 可记录系统的纳秒级时间戳:

  • 串行(serial):单线程依次完成一系列运算
  • 并行(concurrency):通过两个线程同时执行相同运算

以一个代码为例:

package ThreadDemo;
public class demo6 {private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {// 使⽤并发⽅式concurrency();// 使⽤串⾏⽅式serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();// 利⽤⼀个线程计算 a 的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a--;}}});thread.start();// 主线程内计算 b 的值int b = 0;for (long i = 0; i < count; i++) {b--;}// 等待 thread 线程运⾏结束thread.join();// 统计耗时long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {// 全部在主线程内计算 a、b 的值long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) {a--;}int b = 0;for (long i = 0; i < count; i++) {b--;}long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串⾏: %f 毫秒%n", ms);}}


需要注意的是 :多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的。

四:Thread类及常见的方法

 在 Java 中,Thread 类是 JVM 用来管理线程的核心类。每个线程在 JVM 中都对应一个唯一的 Thread 对象,通过这个对象可以对线程的状态、行为进行控制和管理。

4.1:Thread常见的构造方法
构造方法声明参数说明用途
Thread()无参数创建一个线程对象,需通过重写 run() 方法定义线程任务(默认线程名由 JVM 生成,如 Thread-0)。
Thread(Runnable target)target:实现 Runnable 接口的对象(封装线程任务)创建线程并关联任务,推荐使用(解耦线程与任务逻辑,避免单继承限制)。
Thread(String name)name:线程名称(字符串)创建线程并指定名称,便于调试(如日志输出、线程监控时区分不同线程)。
Thread(Runnable target, String name)target:任务对象;name:线程名称同时指定任务和线程名称,兼顾任务解耦与调试便利性。
Thread(ThreadGroup group, Runnable target)group:线程组;target:任务对象将线程加入指定线程组(线程组用于批量管理线程,如统一设置优先级、中断等)。

说明:

  • 最常用的构造方法是 Thread(Runnable target)Thread(Runnable target, String name),通过传入 Runnable 对象分离任务逻辑,更符合面向对象设计。
  • 线程名称可通过 setName() 后续修改,但建议在构造时指定,便于早期调试。
  • 线程组(ThreadGroup)主要用于线程的批量管理,日常开发中使用较少,除非需要对一组线程进行统一操作(如销毁所有子线程)。

那么如何为一个线程修改一下名字呢?以下面代码为例细述

public class demo6{public static void main(String[] args) {Thread t=new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"我的线程1");t.start();}
}

通过jconsole就可以看到我刚刚修改的线程名字
在这里插入图片描述

注意:为什么这个线程中没有看到main,是因为main线程已经执行完了。

  • 在这个代码中start执行完毕,main方法就运行完了
  • main方法结束,对应的main线程就结束了
  • 也就是对于一个线程来说,线程对应的"入口方法"执行完毕,对应的线程就会自动销毁。
4.2:Thread几个常见的属性
属性获取方法说明
IDgetId()线程的唯一标识,不同线程不会重复
名称getName()用于调试工具区分线程,可自定义
状态getState()表示线程当前所处的情况(如 NEWRUNNABLETERMINATED 等)
优先级getPriority()优先级高的线程理论上更容易被调度(范围 1~10,默认 5)
是否后台线程isDaemon()JVM会在一个进程的所有非后台线程结束后,才会结束运行。
是否存活isAlive()简单理解为 run 方法是否运行结束
是否被中断isInterrupted()标识线程是否被中断(需结合 interrupt() 方法协作处理)

这里讲一下是否为后台进程,引入思考那么前台线程又是什么?

前台线程:线程没有运行完,进程就不会结束(即线程可以阻止进程结束)
后台线程:线程没有运行完,进程可以结束(即线程不能阻止进程结束)

应用场景:当一个线程做的任务很重要,必须要完成这个任务,这样就应该设置为前台线程。
如果一个线程做的任务无关紧要/周期性无期限执行,就可以设置为后台线程。

我们可以通过.setDaemon()设置线程为前台还是后台,但是这个操作必须在start之前。


以下面代码为例,详细描述一下现成的存活isAlive()

package ThreadDemo;
public class demo7 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();System.out.println("是否存活:"+t.isAlive());Thread.sleep(4000);System.out.println("是否存活:"+t.isAlive());}
}

在这里插入图片描述
可以看到判断线程是否存活,看线程的状态即可。

  • 线程正在执行,isAlive()就是true的。
  • 线程执行完毕,isAlive()就是false的。
4.3:启动一个线程 - start()

以这个代码为例

package ThreadDemo;
public class demo8 {public static void main(String[] args) {Thread t=new Thread(()->{System.out.println("hello thread");});t.start();System.out.println("hello main");}
}

在这里插入图片描述
当我们执行几次结果后发现,都是先打印hello main 在打印hello thread那么是不是都是一直这个顺序呢?

当start后,main线程和t线程两个执行流,是并发执行的关系。由于操作系统对于线程的调度是随机的,所有也有可能出现先打印hello thread的情况。

重点:一个线程对象只能start一次

当一个线程对象出现一次以上start就会出现如下图所示报错
在这里插入图片描述

4.4:中断一个线程

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
在这里插入图片描述
此处针对lambda表达式定义,实际上实在new Thread()之前的,就会出现t没有进行初始化的报错信息。
我们可以使用Thread提供的一个静态方法currentThread

package ThreadDemo;
public class demo9 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{//由于currentThread方法是在start之后在执行的//并且是在t线程中执行的,返回的结果就是指向t线程对象的引用while(!Thread.currentThread().isInterrupted()){System.out.println("hello thread");}});t.start();Thread.sleep(2000);//在main线程中尝试中止t线程t.interrupt();}
}

这样就可以使我们的t线程结束

但是我们在循环中加入sleep
在这里插入图片描述
就会触发异常
在这里插入图片描述

在这里如果t线程正在休眠,此时的main调用Interrupt方法,就会把sleep提前给唤醒。sleep被提前唤醒,就触发异常,就把isInterrupted标志位给重置为了false。这样我们的线程就不会结束

那么在这种情况下该怎么结束线程呢?
①:可以在e.printStackTrace();后面加上一个break,让线程立即结束

package ThreadDemo;
public class demo9 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{//由于currentThread方法是在start之后在执行的//并且是在t线程中执行的,返回的结果就是指向t线程对象的引用while(!Thread.currentThread().isInterrupted()){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});t.start();Thread.sleep(2000);//在main线程中尝试中止t线程t.interrupt();}
}

在这里插入图片描述
当然也可以把这个e.printStackTrace();注释掉,这样异常就不会打印了
②:也可以通过ideal自动生成的catch: throw new RuntimeException(e),也可以在break之前在添加其他逻辑

这几种方式,本质上都是t线程自己决定自己是否要终止,相当于main只是对t提供了一个“建议”,而不是:“强制执行的”.

使用Interrupt方法的时候:

  • 1:t 线程没有进行sleep等阻塞操作,t 的isInterrupted()方法返回true,通过循环结束t 线程。
  • 2:t 线程进行sleep等阻塞操作 ,还是会返回true,但是sleep如果被提前唤醒InterruptException,同时也会把isInterrupted()返回的结果设置为false,此时就需要手动去决定是否要结束线程。
4.5:线程等待-join()

mian线程等待t 线程结束

package ThreadDemo;
public class demo10 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for(int i=0;i<3;i++){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();System.out.println("主线程等待之前");t.join();System.out.println("主线程等待之后");}
}

在这里插入图片描述

t 线程等待 main 线程结束

package ThreadDemo;
public class demo11 {public static void main(String[] args) throws InterruptedException {Thread myThread=Thread.currentThread();Thread t=new Thread(()->{try {System.out.println("t线程等待之前");myThread.join();System.out.println("t线程等待之后");} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();for(int i=0;i<10;i++){System.out.println("hello main");Thread.sleep(1000);}}
}

在这里插入图片描述

注意这里需要获取main线程的引用:Thread myThread=Thread.currentThread();

  • 哪个线程调用的join,该线程就是等的一方
  • join前面是那个引用,该线程就是被等的一方

join默认的是死等,但是join还有重载版本,可以指定最大的等待时间

方法声明等待时间参数说明
void join(long millis)millis:等待时间(单位:毫秒),0 表示无限等待(与无参 join() 效果一致)
void join(long millis, int nanos)millis:毫秒数(主单位),nanos:纳秒数(范围 0-999999,辅助精度),两者总和为总等待时间

以上面代码为例,加入3000毫秒的最大等待时间

在这里插入图片描述

五:线程的状态
5.1:线程的状态是一个枚举类型 Thread.State

 在 Java 中,线程(Thread)的生命周期包含以下 6 种状态,定义在 Thread.State 枚举类中,如下图所示:

状态名称说明
NEW(新建)线程对象已创建,但尚未调用 start() 方法,未开始执行。
RUNNABLE(可运行)线程已调用 start() 方法,正在 JVM 中执行,或等待 CPU 资源(就绪状态)。
BLOCKED(阻塞)线程因竞争同步锁(synchronized)被阻塞,等待获取锁。
WAITING(等待)线程无限期等待另一个线程的特定操作(如 wait()join() 无参版),需被显式唤醒。
TIMED_WAITING(计时等待)线程在指定时间内等待(如 sleep(time)wait(time)join(time)),超时后自动唤醒。
TERMINATED(终止)线程执行完毕(run() 方法结束)或因异常终止。

可通过 Thread.getState() 方法获取线程当前状态。

package ThreadDemo;
public class demo12 {public static void main(String[] args) {Thread t=new Thread(()->{});System.out.println(t.getState());t.start();t.join();System.out.println(t.getState());}
}

在这里插入图片描述
可以看到还没有开始start时候就是NEW状态,线程执行完了就是TERMINATED
RUNNABLE,当在t.start(); 后加入
System.out.println(t.getState()+" "+t.isAlive());

此时就可以看到RUNNABLE状态,代码中不触发阻塞类操作,都是RUNNABLE状态

在这里插入图片描述
其他三个都是阻塞状态,以下面代码为例

package ThreadDemo;
public class demo12 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});System.out.println(t.getState()+" "+t.isAlive());t.start();System.out.println(t.getState()+" "+t.isAlive());t.join();System.out.println(t.getState()+" "+t.isAlive());}
}

再利用jconsole就可以看到我们main线程的状态
在这里插入图片描述
以及Thread-0的状态,此时t线程使在join操作所以是 TIMED_WAITING状态。

在这里插入图片描述
在这里插入图片描述

状态转换关键点:

  • NEWRUNNABLE:调用 start() 方法后进入可运行状态。
  • RUNNABLEBLOCKED:竞争同步锁失败时进入阻塞状态,获取锁后回到 RUNNABLE
  • RUNNABLEWAITING/TIMED_WAITING:调用 wait()join() 等方法时进入等待状态,被唤醒或超时后回到 RUNNABLE
  • 所有状态 → TERMINATED:线程执行完成或异常终止后进入终止状态,无法再切换到其他状态。

http://www.dtcms.com/a/515120.html

相关文章:

  • JavaEE初阶——HTTP/HTTPS 核心原理:从协议格式到加密传输
  • Linux 内存 get_user_pages_remote 函数
  • 【图像处理】图像滤波
  • CSS 列表详解
  • 建设工程规范下载网站商城网站开发的完整流程
  • 同德县网站建设公司海南网站建设及维护
  • 广西送变电建设公司网站深圳市建设工程造价站官网
  • 网站获取访问者qq号码专业的网页设计和网站制作公司
  • 网站建设费账务处理a站下载
  • 哈尔滨网站建设丿薇建立短语
  • 徐州seo网站推广网站开发 页面功能布局
  • 用extjs做的网站wps如何做网站
  • 青羊区建设局网站怎样入驻微信小程序
  • 网站标题几个字合适学生个人网页制作html代码
  • 网站设计规划信息技术教案枣庄三合一网站开发
  • 提升网站流量电子邮件免费注册
  • 广东住房和城乡建设厅官方网站运维工程师累吗
  • 宁波江北区建设局网站如何用wordpress上传根目录
  • 建设实业公司网站设计模板哪家网站做民宿好
  • 网站质量需求页面设计的网站
  • 有经验的南昌网站制作小白 宝塔 wordpress
  • 网站建设引入谷歌地图wordpress淘宝内容
  • 国外网站做网上生意哪个好创可贴网页设计网站
  • 福田附近做网站公司线上销售平台都有哪些
  • app网站开发成本哈尔滨seo优化客户
  • 建设网站的项目策划书网站建设毕业设计模板
  • 微网站自己怎么做的大连旅游网站建设
  • 玉林市建设工程交易中心网站怎样创建自己的公众号
  • 企业网站内使用了哪些网络营销方式线上编程课推荐哪一个
  • 我的世界做神器指令网站wordpress sso插件开发