线程的四种操作

2024-09-09 04:52
文章标签 线程 操作 四种

本文主要是介绍线程的四种操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  所属专栏:Java学习     

在这里插入图片描述

 

1. 线程的开启

start和run的区别:

run:描述了线程要执行的任务,也可以称为线程的入口

start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api会在内核中创建线程)

start执行的速度是比较快的,一旦 start 执行完毕,新线程就会开始执行,调用start的线程,main线程也会继续执行

在Java中,一个Thread对象只能对应到一个系统中的线程,在start中就会根据线程的状态来判断,如果Thread对象没有start,此时的状态就是一个new状态,可以顺利调用start,如果已经调用过start,就会进入到其他状态,只要不是new状态,都会抛出异常

2. 线程的终止

当线程B正在运行时,如果发生了特殊情况需要终止掉线程,有两种实现方式:

  1. 通过共享的标记来进行沟通
  2. 调用interrupt()方法来通知

先来看使用自定义的isQuit作为标志位的例子:

public class ThreadDemo4 {private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(){@Overridepublic void run() {while (!isQuit){System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("thread线程终止了");}};thread.start();Thread.sleep(1000);System.out.println("main线程尝试终止thread线程...");isQuit = true;}
}

这时就有一个问题需要注意:

如果isQuit不用static修饰,改为main方法里的局部变量可行不可行?ans:肯定是不可行的

这就涉及到了lambda表达式变量捕获的问题了

变量捕获是lambda表达式/匿名内部类中的一个语法规则:isQuit和lambda表达式定义在一个作用域中,此时lambda内部是可以访问到外部(和lambda同一个作用域)中的变量的,Java中的变量捕获是有一个特殊要求的,要求捕获的变量必须是final或事实final(虽然不是final修饰,但是后面没有更改)

就如上面的代码中,isQuit后面是被修改了的,所以就违反了语法规则

刚开始的形式为什么可以:写在外面就是外部类的成员变量,内部类本来就是可以访问外部类的

再来看interrupted的例子:

Thread类里面有一个boolean类型的成员变量interrupted,初始状态为false,一旦外面有线程调用interrupt方法,就会设置标志位为true

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

看起来是和自定义的标志位一样的,但是运行之后就会发现出现了异常:

由于在循环中判断和打印的操作太快了,整个循环的时间都是花在sleep方法里的,当main中调用interrupt时,大概率线程thread是在sleep中的,此时Interrupt就不仅能设置标志位,还可以唤醒thread线程,就会抛出InterruptedException异常,catch中捕获到异常也是做了抛出处理,就交给了JVM,程序就异常终止,那么把处理异常的方式更改一下试试:

这时就会发现while循环中的代码一直在执行

这里的原因是当sleep等阻塞函数被唤醒之后,就会清空刚刚设置的标志位,这时interrupted就一直是初始状态,也就导致了死循环,如果需要结束循环,可以把刚刚的异常处理直接改为break。

Java中终止线程的方式:A线程希望B线程能够终止,B线程收到这样的请求之后就可以自行决定是否是立即终止,稍后终止还是直接无视

  1. 如果B线程想要无视A,catch中就什么也不做,B线程就继续执行(sleep清除标志位)
  2. 如果B线程想要立即结束,就在catch中直接break或return
  3. 如果B线程想要稍后再终止,就可以在catch中添加其他的逻辑(释放资源,清理数据,提交结果...),这些完成之后再break/return.

3. 线程的等待

在之前提到过,操作系统针对多个线程的执行是一个“随机执行,抢占式调度”的过程,哪条线程先执行和先结束是不确定的,不过可以通过使用线程等待来决定哪条线程先结束,也就是让最后结束的线程等待先结束的线程,此时后结束的线程就进入了阻塞状态

例如在a线程中调用b.join(),就是让a线程等待b线程先结束,然后a再继续执行

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

上面的代码是thread1线程先执行,然后main线程开始等待,进入阻塞状态,如果说修改为先让thread1线程结束,main线程再开始等待会如何呢?

此时join并没有发生阻塞,join方法就是确保被等待的线程能够先结束,如果已经结束了,就没有等待的必要了

此外,任何线程都可以等待别的线程,而且可以等待多个线程,或者是多个线程之间互相等待

public class ThreadDemo7 {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) {throw new RuntimeException(e);}}System.out.println("线程t1结束");});Thread t2 = new Thread(()->{for(int i = 0;i < 3;i++){System.out.println("线程t2执行...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程t2结束");});t1.start();t2.start();System.out.println("main线程开始等待...");t1.join();t2.join();System.out.println("main线程结束等待");}
}

这就是main线程同时等待两个线程的例子,关于两个join的顺序其实没有区别,最终都是等待了4s

在main等待t1和t2同时,t2也可以等待t1:

这样就把原来t1和t2并发执行修改为了t1先执行

main线程也可以被其他线程等待,不过写法不同的是,需要先获取main线程的引用

public class ThreadDemo8 {public static void main(String[] args) throws InterruptedException {Thread mainThread = Thread.currentThread();Thread thread = new Thread(()->{System.out.println("thread等待main线程");try {mainThread.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("结束等待");});thread.start();Thread.sleep(1000);System.out.println("main线程结束");}
}

在上面使用的join方法中,由于是没有传入参数的,就表示被等待的线程只要没有执行完,就会一直等待,这种方式肯定是不好的,如果被等待的线程出现问题了,就会使这个等待操作一直进行,所以就有了传参的版本,但上面列举的第三个高精度的一般也用不到

4. 线程的休眠

之前一直用的Thread.sleep()这个操作就是让调用的线程阻塞等待一定时间的,线程执行sleep之后,就会使这个线程不参与CPU的调度,把CPU的资源让出来,给其他线程使用,在开发时,如果发现某个线程的CPU占用率过高,就可以通过sleep来改善,虽然说线程的优先级也可以影响,但比较有限

在这里插入图片描述

这篇关于线程的四种操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1150210

相关文章

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Python 中的 with open文件操作的最佳实践

《Python中的withopen文件操作的最佳实践》在Python中,withopen()提供了一个简洁而安全的方式来处理文件操作,它不仅能确保文件在操作完成后自动关闭,还能处理文件操作中的异... 目录什么是 with open()?为什么使用 with open()?使用 with open() 进行

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Java利用JSONPath操作JSON数据的技术指南

《Java利用JSONPath操作JSON数据的技术指南》JSONPath是一种强大的工具,用于查询和操作JSON数据,类似于SQL的语法,它为处理复杂的JSON数据结构提供了简单且高效... 目录1、简述2、什么是 jsONPath?3、Java 示例3.1 基本查询3.2 过滤查询3.3 递归搜索3.4

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

Python使用DrissionPage中ChromiumPage进行自动化网页操作

《Python使用DrissionPage中ChromiumPage进行自动化网页操作》DrissionPage作为一款轻量级且功能强大的浏览器自动化库,为开发者提供了丰富的功能支持,本文将使用Dri... 目录前言一、ChromiumPage基础操作1.初始化Drission 和 ChromiumPage

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件