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

【JavaEE初阶】多线程(2)

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

线程的核心操作

创建线程start

线程的状态

线程的终止

定义一个变量实现

利用标志位实现

加上break/return 结束线程 

线程等待

join 无参数版本

两个线程等待

多个线程等待

缺陷

join有参数版本


线程的核心操作

创建线程start

一个经典的面试题:start和run之间的区别

  • start:调用系统函数,真正在系统内核中创建线程(创建PCB,加入到链表中),此处的start 会根据不同的系统,分别调用不同的api(windows,linux,mac...),创建好新的线程,再单独来执行run.
  • run:描述了线程要执行的任务,也可以称为"线程的入口"

start的执行速度一般是比较快的(创建线程,比较轻量),一旦start执行完毕,新线程就会开始执行,调用start的线程,也会执行main. 调用start,不一定非得是main线程调用,任何线程都可以创建其他线程,如果系统资源充裕,就可以任意的创建线程(但线程不是越多越好)

start的本质调用系统的api,系统的api会在系统内核里 ,创建线程(创建PCB,加入链表)

线程的状态

由于Java希望一个Thread对象 只能对应到一个系统中的线程,因此会在start中 根据线程的状态做出判定:

  • 如果Thread对象 没有调用过start,此时状态是一个NEW状态,接下来就可以顺利调用start
  • 如果已经调用了start,就会进入到其他状态,只要不是NEW状态,接下来start就会抛出异常

 所以再次强调: 一个Thread对象,只能调用一次start,如果多次调用就会出问题(一个Thread对象,只能对应系统中的一个线程)

线程的终止

Java中让一个线程结束是一个更'温柔'的过程,比如B正在运行着,A想让B结束,其实核心是A想办法(如何将B的run能够更快的执行完)让B的run方法执行完毕,此时B自然结束了, 而不是B的run执行一半,A直接把B强制结束.

定义一个变量实现

public class Demo6 {private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while(! isQuit){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t线程结束.");});t.start();Thread.sleep(1000);System.out.println("main线程尝试终止 t线程");
//修改isQuit变量,就能够影响到t线程结束isQuit=true;}
}

其中判断条件isQuit要写在main方法的外面 作为成员变量,内部类(lambda表达式本质上是一个'函数接口'产生的 匿名内部类)是可以访问外部类的成员

知识复习: 变量捕获

变量捕获是lambda表达式/匿名内部类 的一个语法规则

isQuit 和 lambda定义在一个作用域中,此时的lambda内部是可以访问到lambda外部 的,Java把同作用域中的所有变量都给捕获了,但是Java的变量捕获要求捕获的变量得是final/ 事实final(这是Java的特殊限制,c++,js等语言没有这个限制)

利用标志位实现

Interrupt方法能够设置标志位, 也能唤醒sleep等阻塞方法.

sleep被唤醒之后,又能清空标志位

public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread t =new Thread(()->{Thread currentThread =Thread.currentThread();while(!currentThread.isInterrupted()){System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();Thread.sleep(3000);//在主线程中 控制t线程被终止,设置上述标志位t.interrupt();}
}

Thread类里面有一个成员变量 是interrupted (Boolean类型),初始情况下这个变量是false(未被终止),但是一旦外面的其他线程调用一个interrupt方法,就会设置上述标志位

修改代码,停止报错

加上break/return 结束线程 

 所以,Java中 终止线程是一个'温柔'的过程,不是强行终止 ,举例: 如果A希望B线程终止,B收到这样的请求后,B需要自行决定 是否要终止/立即 /稍后 终止(B线程内部的代码自行决定,其他线程无权干涉)

  • 如果B线程想无视A,就直接catch中,啥都不做,B线程仍然会继续执行(sleep清除标志位,就可以使B能够做出这样的选择,否则B势必会结束)
  • 如果B线程想立即结束,就直接在catch中加上return或者break.此时B线程会立即结束
  • 如果B线程想稍后结束,就可以在catch中加上一些其他的逻辑(比如 释放资源,清理一些数据,提交一些结果...收尾工作)  这些逻辑之后,再进行return或者break.

线程等待

操作系统 针对多个线程的执行,是一个 '随机调度,抢占式执行'过程, 线程等待 就是在确定两个线程的'结束顺序'(无法确定两个线程调度执行的顺序,但可以控制 谁先结束,谁后结束), 本质上是让后结束的线程 等待先结束的线程即可(此时后结束的线程就会进入阻塞,一直到先结束的线程真的结束了,阻塞才解除)

join 无参数版本

对于join来说,无参数版本会持续等('死等'). 被等待的线程,只要不执行完,这里的等待就会持续阻塞

两个线程等待

使用join 比如,有两个线程a和b,在a中调用b.join,表示让a线程等待b线程先结束,然后a再继续执行(b是被等待的一方,先结束)

public class Demo8 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{for (int i = 0; i < 3; i++) {System.out.println("这是线程t");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t线程结束");});t.start();//让主线程 等待t线程System.out.println("main线程开始等待");t.join();System.out.println("main线程等待结束");}
}

如果上面的 t线程先结束了,main线程就不必等待了(join就不会发生阻塞),join 就是确保被等待的线程能够结束,如果已经结束了,join就不必在等了.

任何的线程之间都是可以相互等待的

下面是 t线程等待 主线程的代码示例

public class Demo10 {public static void main(String[] args) throws InterruptedException {//t线程等待主线程Thread mainThread =Thread.currentThread();//拿到主线程对象Thread t =new Thread(()->{System.out.println("t线程开始等待...");try {mainThread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t线程等待结束");});t.start();Thread.sleep(2000);System.out.println("main线程执行结束.");}
}

 

获取主线程对象 和 sleep的理解:

多个线程等待

相互等待的线程 也不一定是两个线程之间,一个线程也可以同时等待多个别的线程,或者若干个线程之间也能相互等待

public class Demo9 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 3; i++) {System.out.println("线程t1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1执行结束");}) ;Thread t2 =new Thread(()->{for (int i = 0; i < 4; i++) {System.out.println("线程t2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2执行结束");});t1.start();t2.start();System.out.println("main线程开始等待...");t1.join();t2.join();System.out.println("main线程结束等待.");}
}

上述代码中,主线程等待t1和t2线程,t2线程等待t1,所以结束顺序是t1, t2, main线程

t1, t2和主线程这三个日志的顺序是不确定的(不确定哪个先执行),但是由于t1和t2有创建开销,一般来说是主线程先执行,t1和t2 的顺序不确定.

缺陷

对上述 join无参数版本会出现'死等'的状态   一旦被等待线程代码 出现了一些bug,就可能使这个线程迟迟无法结束,从而使等待的线程一直无法执行其他操作.

join有参数版本


http://www.mrgr.cn/news/21362.html

相关文章:

  • 使用Node-API进行异步任务开发
  • 执行任务赚积分
  • 成功的原则
  • Spring MVC 执行流程详解
  • 最优化方法Python计算:求解约束优化问题的拉格朗日乘子算法
  • python图像处理的图像几何变换
  • HivisionIDPhoto V2 - AI一键智能制作生成证件照 新增抠图模型,优化抠图效果 本地一键整合包下载
  • Java面试篇基础部分-JVM内存运行时机制
  • [羊城杯 2021]Ez_android-快坚持不下去的第五天
  • 多云架构下大模型训练的存储稳定性探索
  • 音视频开发之旅(92)-多模态Clip论文解读与源码分析
  • exit与_exit详解,并于进程间的关系
  • 实例讲解Simulink油门踏板信号解析及故障判定模型搭建方法
  • Docker 容器技术:简化 MySQL 主从复制部署与优化
  • 久久派简单搭建小游戏网站
  • ABB机器人无限解包( rawbytes)
  • Pytorch多GPU分布式训练代码编写
  • box64 安装
  • 2024 年高教社杯全国大学生数学建模竞赛B题第三问详细解题思路(终版)
  • SpringTest框架JUnit单元测试用例获取ApplicationContext实例的方法