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

多线程——Thread 类的基本用法

线程Tread类介绍:

  我们知道,线程是操作系统提供的概念,操作系统一般会提供一些api(Application Programming Interface)来给程序员进行有关线程的操作,每个操作系统提供的线程api都不一样。为了解决统一性的问题,java中就独立封装一个Thread类,以便与程序员可以直接使用这个类来操作线程。

  Thread类是Java多线程编程的核心类,位于java.lang包中。‌ Thread类与Runnable接口一起,为Java程序提供了创建和管理线程的能力。线程是程序中独立执行的路径,允许程序同时执行多个任务,从而提高程序性能‌。

  线程从创建到消亡会经历不同的状态,包括创建、就绪、运行、阻塞、主动睡眠、等待唤醒和消亡。线程的上下文切换在多核处理器系统中提高了程序的并行执行效率‌。

由图可知,Thread类是实现Runnable接口的,而Runnable接口里有一个抽象方法run。可以理解为Runnable是一个任务,Thread类的构造方法中也可以直接传一个Runnable类型的参数进去实现线程的run方法,启动线程是直接执行run方法

以下是Thread类的构造方法和run方法的介绍:

以下是Thread类的几个常见属性

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

2.名称是其他的调试工具用到

3.状态表示当前线程处在什么样的情况如:就绪、运行、阻塞、主动睡眠、等待唤醒和消亡等情况

4.优先级高的理论上容易先被调度到

5.JVM会在一个进程的所有非后台线程结束之后,才会结束运行,后台线程的提前结束不影响。

6.我们自行创建的线程一般都是前台线程,我们可以自行修改为后台线程,调用setDaemon()方法,参数为true,则设置为后台线程

7.是否存活简单说明就是run方法是否执行结束了

线程创建

在线程main中,创建的线程是和main是并发执行的,由于调度问题执行顺序也会有所差异。

我们通过线程调用start方法来启动线程:

线程名.start();

(这才是真正的创建线程,JVM会调用操作系统的API来完成线程的创建,同时会执行线程的入口方法run,线程创建好后自动去调用)

创建线程有许多种方法,以下我们就介绍五种方法:

1.继承 Thread, 重写 run

创建MyTread类的实例

调用start方法启动线程

2.实现 Runnable, 重写 run

传参一个Runnable类

3.继承 Thread, 重写 run, 使用匿名内部类

4.实现 Runnable, 重写 run, 使用匿名内部类

5.使用 lambda 表达式

一起启动上述代码的五个线程,包括main线程,可以看到下图,都是并发执行:

线程中断

我们前面已经介绍了启动线程,现在讲解一下线程的终断:

所谓线程的中断,并不是单纯的中途暂停,而是彻底终止

我们先来看个俩个例子:

1.将使用改变变量(标志位)的方式结束线程

定义俩个线程:

先在外层定义一个全局变量(标志位),然后在t2线程通过修改标志位的方式终止t1线程。

可以看到,我们成功终止掉了t1线程,但是这里有个问题:

能否使用局部变量呢?

如图所示,我们要是将标志位改成局部变量的话,这里会有个报错信息。

我们要先了解到,lambda表达式可以当做一个回调函数,一般的执行时机是在很久之后(当操作系统真正创建线程之后,才会执行),可能线程创建完,main线程就已经结束了,对应的局部变量就已经销毁了。

为了解决这样的问题,所以java中涉及到一个“变量捕获”的操作,就是把被捕获的变量拷贝一份给lambda表达式中,无论外面的变量是否销毁,lambda都能正常使用。

但是有个前提,被捕获的变量,这个变量要不然就是被final所修饰,要不然就是在使用前确保不被修改,若是像如图的t2线程里一样修改了,会导致拷贝前和拷贝后的变量数值不一致,从而出现问题,若是像t1一样没有修改,虽然说成员变量已经销毁,但是只要lambda表达式没有被执行,或者没有被GC垃圾回收,这些变量会继续存在

这是使用局部变量所需要注意的问题,我们可以使用volatile这个关键字修饰局部变量,使这个共享变量具有可见性,保证修改的值会立刻更新到主内存中(这个后续在解决线程安全问题会提到)。

我们之前使用的成员变量作为标志位的方法,这个就不会涉及到“变量捕获”,而是内部类访问外部类的成员,lambda本质上是一个函数式接口,可以当成内部类,内部类可以直接访问外部类的成员变量,不用局限于final。

2.使用Thread类自带的功能代替标志位

我们先来看看常见的几种中断线程的方法

interrupted是静态方法,由类名直接调用即可,而isInterrupted是类方法,需要获取当前的对象再进行调用(获取当前对象方法:Thread.currentThread())

但是当我们运行起来又会发现一个问题:

在这里我们发现sleep抛出了一个异常,表示sleep被异常唤醒了。

原因是因为在t1循环中大部分时间都在sleep休眠状态,此时我们突然调用了interrupt方法终止了t1线程,t1线程被异常唤醒终止导致了sleep抛出了异常。

在catch中我们做的只是向更高一级抛出异常,最终会使JVM发现异常从而报错。

如果我们在catch这里什么都不做呢?

可以看到当我们终止了线程之后t2线程结束,但是t1线程好像不听使唤还在继续运行。

这是因为调用interrupt后标志位被设置成了true,但是会导致sleep提前唤醒,sleep提前唤醒之后导致线程继续执行就把标志位设回了false,下次判断就依旧继续执行。

要想解决上述问题,我们可以处理一下sleep的异常,就是跳出循环:

可以看到,sleep的异常问题就算是解决了。

线程等待

在多线程中,我们可能需要等待另外一个线程完成工作以后才继续执行当前线程,因此我们需要一个方法来完成这样的工作。

t.join();(在当前线程等待一个t线程)

示例:让main线程等待t1线程

可以看到,待t1线程执行完才结束main线程,其他带参数版本也是如此,若是怕等待时间过长可以设计最大等待时间。

线程休眠

获取线程引用

public staticThread currentThread();返回当前线程对象引用

例:

这里的Thread可以看做和this一样的用法,代表当前的引用对象


http://www.kler.cn/news/367996.html

相关文章:

  • 在Spring Boot中配置Map类型数据
  • 医学数据分析中的偏特征图可视化
  • 基于大数据 Python+Vue 酒店爬取可视化系统(源码+LW+部署讲解+数据库+ppt)
  • python如何基于numpy pandas完成复杂的数据分析操作?
  • 华为:高级ACL 特定ip访问特定ip命令
  • XJ07、消费金融|信贷还款的基本种类及其系统交互
  • 安灯系统助力汽车零部件工厂快速解决生产异常
  • python 深度学习 项目调试 图像分割 detectron2
  • 32位的ARMlinux的4字节变量原子访问问题
  • sv标准研读第十九章-功能覆盖率
  • konva不透明度,查找,显示,隐藏
  • ThreadPoolExecutor可以创建哪是哪三种线程池呢?
  • linux网络编程4——WebSocket协议及服务器的简易实现
  • 苏州金龙技术创新赋能旅游新质生产力
  • Navicat导入Excel数据时数据被截断问题分析与解决方案
  • 论文阅读与写作入门
  • mit6824-03-GFS论文记录
  • 微信小程序版本更新管理——实现自动更新
  • Linux复习-C++
  • vue3组件通信--props
  • 虚拟现实新纪元:VR/AR技术将如何改变娱乐与教育
  • 桥接模式,外界与主机通,与虚拟机不通
  • 提示词高级阶段学习day3.3如何写好结构化 Prompt ?
  • AndroidStudio Koala更改jdk版本 2024-1-2
  • 关于我的数据库——MySQL——第二篇
  • Qt/C++路径轨迹回放/回放每个点信号/回放结束信号/拿到移动的坐标点经纬度