【JavaEE 初阶(一)】初识线程

embedded/2024/9/23 7:27:57/

❣博主主页: 33的博客❣
▶️文章专栏分类:JavaEE◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多线程知识

在这里插入图片描述

目录

  • 1.前言
  • 2.进程
  • 3.线程
  • 4.线程和进程的区别
  • 5.Thread创建线程
    • 5.1继承Thread创建线程
    • 5.2实现Runnable接口
    • 5.3lambda表达式
  • 6.Thread常见方法
    • 6.1Thread常见属性
    • 6.2 中段一个线程- interrupt()
    • 6.3等待线程-jion()
  • 7.线程的状态
    • 2.1代码实现
    • 2.2运行结果

1.前言

我们设想:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,那么在Java中该如何实现多线程呢?我们一起来学习吧!!


2.进程

提到线程,我们就不得不先了解进程了,那么什么是进程呢?进程就是运行起来的一个个程序。 引入进程有什么作用呢?是为了实现并发编程。但进程有很多,系统是如何管理它们的呢?那就是"先描述,再组织"

描述:使用PCB结构表示进程的各种属性。
PCB中的重要特性: 1.pid(进程标识符)2.内存指针(表示进程持有的内存资源,比如内存空间具体在哪里,有哪些部分组成,每个部分干啥的等等)3.文件描述表(表示进程持有的硬盘资源,类似于顺序表)4.CPU资源(状态、优先级、上下文、记账消息,这些都主要来完成进程调度)

组织:利用双显链表,把PCB的这些结构串起来。
进程是资源分配的基本单元,创建进程需要给他分配内存和硬盘资源需要消耗的时间太多,销毁一个进程销毁的时间很多,调度一个进程消耗的时间也很多,如果频繁的创建或者销毁一个资源,这个时候的开销就是非常大的,为了解决这个问题,我们引入了线程(Thread)。


3.线程

线程也叫做“轻量级进程”,创建线程,销毁线程,调度线程都比进程更快。但线程不能独立存在,要依附于进程,即一个进程至少包含一个线程!但每一个线程都是独立执行的,一个进程,最开始的时候必须要包含一个线程,这个线程负责完成执行代码的工作,也可以创建出多个线程,完成并发执行的效果。

线程的特点
1.每一个线程都可以独立的去cpu上执行
2.同一个进程的多个线程之间公用一份内存空间和文件资源
3.同一个进程中,采用多线程的方式能提高效率,但有一定限度,如果无限度的引入线程可能是调度开销增大,导致效率反而降低
4.同一个进程中,一个线程抛出异常,如果没有妥善处理可能会导致整个进程崩溃。


4.线程和进程的区别

1.一个进程包含一个或多个线程
2.进程和线程都是用来实现并发编程的,但线程比进程更轻量,更高效
3.进程和进程之间具有独立性,如果一个进程异常不会影响到其他进程,但线程线程之间可能会相互影响
4.进程是资源分配的基本单位,线程是调度执行的基本单位


5.Thread创建线程

线程是操作系统的概念,操作系统提供了一些API可以操作线程,Java对系统API进行了封装,使用Thread类就可以创建出一个线程.

5.1继承Thread创建线程

方法1:

java">package Thread;
class MyThread extends Thread{@Override//线程的入口public void run() {while (true) {System.out.println("hello thread");//隔1s轮转try {Thread.sleep(1000);//1s} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();//start和run都是Thread的成员//run只是描述线程的入口,线程主要做什么//start则是真正的调用了系统API创建了线程,让线程再调用run()t.start();while (true){System.out.println("hello main");Thread.sleep(1000);}}
}

方法2:匿名内部类

java">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) {e.printStackTrace();}}}};t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

注意
1.Thread在java.lang这个包下并不用导包
2.start方法内部会调用系统的API生成一个线程再调用run函数,和run函数的效果类似,但本质区别为:是否会创建一个线程


5.2实现Runnable接口

方法1:

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

方法2:匿名内部类

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

5.3lambda表达式

java">package thread;
public class Demo6 {public static void main(String[] args) {//lambda表达式Thread t = new Thread(() -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"第一个线程");t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

6.Thread常见方法

6.1Thread常见属性

属性获取方法
IDgetId
名称getName
状态getState
优先级getPriority
是否后台线程isDaemon
是否存活isAlive
是否被中断isInterrupted

ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况,下面我们会进一步说明
优先级高的线程理论上来说更容易被调度到
后台线程:守护线程,后台线程结束与否不影响整个程序,但如果前台线程没有结束,进程也不会结束。
是否存活:判断内核线程是否已经销毁

6.2 中段一个线程- interrupt()

首先我们要知道,一个线程如果时间特别长,那么大概率在线程的内部有循环再一直执行。如果想要中断此线程,那么我们就想办法尽快让run函数执行完成,那么怎么能让循环快速执行完呢?其实我们只需要在循环处添加一个条件,条件不成立就结束了。
例如:

java">public class Demo7 {public static boolean  flag=false;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!flag){System.out.println("t进程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("工作完毕");});t.start();Thread.sleep(5000);flag=true;System.out.println("打断进程");}
}

注意
定义flag的时候只能定义为成员变量,不能定义为局部变量,因为在lambda有一个语法规则:变量捕获。把当前作用域的变量在lambda中复制了一份,在变量捕获时有一个前提限制:必须只能捕获final修饰的变量或者变量不能做任何修改。如果把flag设置为成员变量,就不再是变量捕获的与法律,而是内部类访问外部类的属性。

但是如果我们每次都专门定义一个标志位来打断线程是非常麻烦的,而且当处于睡眠模式下还不能立即就想应,在Thread类中有有一个标志位isInterupted来判定线程是否被打断。

java">public class Demo8 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("t进程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//唤醒sleep有3种操作//1.不管它,继续执行t线程//2.立即执行打断break;//3.进行一些其他操作}}});t.start();System.out.println("main进程");Thread.sleep(5000);System.out.println("t进程打断");t.interrupt();//打断}
}

说明
1.Thread.currentThread()表示当前线程即t线程
2.在正常情况下,sleep休眠时间完成才会被唤醒,如果调用interrupt()那么就提提前唤醒它触发InterruptedException异常
观察下图:虽然打断了t线程并且触发了InterruptedException异常,但t线程依然在执行。
在这里插入图片描述
出现上述情况是因为:interr唤醒sleep之后会抛出异常当同时也会清除标志位,这就使打断效果像没有生效一样。Java期望当线程收到“要中断”的信号使,由本身来决定接下来该怎么做。

6.3等待线程-jion()

等待一个线程即使指,当一个线程执行完毕时才能执行另外一个线程

java">public class Demo9 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for (int i=0;i<5;i++){System.out.println("t线程正在执行");}});t.start();//等待t线程执行结束再执行main线程t.join();System.out.println("t线程执行结束");System.out.println("main线程");}
}

7.线程的状态

在进程中最核心的状态就是就绪和阻塞状态,在线程中同样适用,同时java又赋予了线程一些其他的状态:

NEW:创建Thread对象安排了工作,但没有启动。
RUNNABLE:指就绪状态,即线程正在执行或者线程等待执行
TERMINATED: 工作完成了
TIMWD_WAITING:阻塞,由于sleep这种固定的时间方式而阻塞
WAITING: 阻塞,由于wat这种不固定的时间方式而阻塞
BLOCKED: 阻塞,由于锁竞争导致的阻塞
在Java中可以通过getState()方法来查看此时线程的状态。

2.1代码实现

java">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("t线程在执行");}});System.out.println(t.getState());t.start();System.out.println(t.getState());Thread.sleep(3000);System.out.println(t.getState());}
}

2.2运行结果

在这里插入图片描述

总结:本篇文章主要介绍了线程的概念,为什么要引入线程,要理解线程和进程的区别,创建线程的几种方法,会运用Thread的一些常见方法。

下期预告:线程安全问题


http://www.ppmy.cn/embedded/33509.html

相关文章

2024五一赛数学建模A题B题C题完整思路+数据代码+参考论文

A题 钢板最优切割路径问题 &#xff08;完整资料在文末获取&#xff09; 1. 建立坐标系和表示方法&#xff1a; 在建模之前&#xff0c;我们需要将切割布局转换为数学表示。首先&#xff0c;我们可以将布局中的每个点表示为二维坐标系中的一个点。例如&#xff0c;B1可以表示…

数据分析:基于DESeq2的转录组功能富集分析

介绍 DESeq2常用于识别差异基因&#xff0c;它主要使用了标准化因子标准化数据&#xff0c;再根据广义线性模型判别组间差异&#xff08;组间残差是否显著判断&#xff09;。在获取差异基因结果后&#xff0c;我们可以进行下一步的富集分析&#xff0c;常用方法有基于在线网站…

神经网络参数初始化

一、引入 在深度学习和机器学习的世界中&#xff0c;神经网络是构建智能系统的重要基石&#xff0c;参数初始化是神经网络训练过程中的一个重要步骤。在构建神经网络时&#xff0c;我们需要为权重和偏置等参数赋予初始值。对于偏置&#xff0c;通常可以将其初始化为0或者较小的…

Vue的项目启动指令分析

通过Vue CLI脚手架创建的项目&#xff0c;默认的启动项目方式是 npm run serve 这里的serve是可以修改的。 在创建的项目目录中&#xff0c;找到package.json 双击打开&#xff0c;找到scripts部分 在scripts部分&#xff0c;有一个"serve"键值对&#xff0c;这里的…

Linux专栏09:Linux基本指令之时间日期指令及关机重启指令

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Linux专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章题目 编号&#xff1a;09 文章目录 文章题目六、时间相关指令1…

通义灵码实战系列:一个新项目如何快速启动,如何维护遗留系统代码库?

作者&#xff1a;别象 进入 2024 年&#xff0c;AI 热度持续上升&#xff0c;翻阅科技区的文章&#xff0c;AI 可谓是军书十二卷&#xff0c;卷卷有爷名。而麦肯锡最近的研究报告显示&#xff0c;软件工程是 AI 影响最大的领域之一&#xff0c;AI 已经成为了软件工程的必选项&…

探索AI工具的巅峰:个人体验与深度剖析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

第11章 软件工程

这里写目录标题 1.软件过程1.1能力成熟度模型(CMM)1.2能力成熟度模型集成(CMMI)1.3瀑布模型(线性顺序)1.4增量模型1.5演化模型1.5.1原型模型1.5.2螺旋模型 1.6喷泉模型1.7统一过程(UP)模型 2.敏捷方法3.系统设计4.系统测试4.1单元测试(模块测试)4.2集成测试4.3黑盒测试(功能测试…