Java并发编程之线程基础

线程通知与等待

Java中的Object类是所有类的父类,鉴于继承机制,Java把所有类都需要的方法放到了Object类里面,其中就包括了线程的通知和等待。

wait以及notify

当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起。直到发生下面几件事情之一才返回。

  • 其他线程调用了该共享对象的notify()或者notifyAll()。
  • 其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

另外,需要注意的是,如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()时调用线程会抛出IllegalMonitorStateException异常。

一个线程如何获取一个共享变量的监视器锁

  • 方法1:执行sychronized同步代码块时,使用该共享变量作为参数
 sychronized(该共享变量){}
  • 方法2:调用该共享变量的方法,并且该方法使用了sychronized修饰。
 sychronized void method{}

虚假唤醒:一个线程即使没有被其他线程调用notify()、notifyAll()进行通知或者被中断,等待超时也可以从挂起状态变为可以运行状态,这就是所谓的虚假唤醒。

虽然虚假唤醒在应用实践中很少发生,但要防患于未然,做法就是不停的去测试该线程被唤醒的条件是否满足,不满足则继续等待。也就是说在一个循环中调用wait()方法进行防范,退出循环的条件是满足了唤醒该线程的条件。

synchronized (obj) {while (条件不满足){obj.wait() ;}
}

实例

其中queue为共享变量,生产者线程在调用queue的wait()方法之前,使用sychronized关键字拿到了该共享变量queue的监视器锁,所以调用wait()方法才不会抛出IllegalMonitorStateException异常。如果当前队列没有空闲容量则会调用queued的wait()方法挂起当前线程,这里使用循环就是为了避免虚假唤醒问题。假如当前线程被虚假唤醒了,但是队列还是没有空余容量,那么当前线程还是会调用wait()方法把自己挂起。

//生产线程
synchronized (queue) {
//消费队列满,贝等待队列空闲
while (queue.size() == MAX SIZE) {try { //挂起当前线程, 并释放通过同步块获取的 queue上的锁,上消费者线程可以获取该锁,然后获取队列里面的元素queue .wait() ;} catch (Exception ex) {ex.printStackTrace() ;}
}
// 空闲则生成元素,并通知消费者线程queue.add(e);queue.notifyAll();
}//消费者线程
sychronized(queue){while(queue.size == 0){try{//挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取该锁,将生产元素放入队列queue.wait();}catch(Exception e){e.printStackTrace() ;}}//消费元素,并唤醒生产者线程queue.take();queue.notifyAll();
}

此外,当前线程调用共享变量的wait()方法后,只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁不会释放。

如下所示:

//创建共享资源
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
new Thread(()->{try{sychronized(resourceA){sychronized(resourceB){// 此处线程A只释放了获取到的resourceA的锁,resourceB的锁并为释放resourceA.wait();   }}}}).start();

wait(long timeout)

该方法相比wait()多了一个超时参数,他的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的timeout ms时间内被其他线程调用该共享变量的notify()或者notifyAll()唤醒,该函数会因为超时而返回。如果将timeout设置为0,则不限制超时时间。

notify()

一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。

此外,被唤醒的线程不能马上从wait方法返回并继续执行,他仍然需要和其他线程一起竞争该锁,只有竞争到了共享变量的监视器锁后才可以继续执行。

notifyAll()

不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。

等待线程执行终止的join方法

在主线程里面启动两个子线程,然后分别调用他们的join()方法,那么主线程首先会在调用theadOne.join()方法后被阻塞,等待theadOne执行完毕后返回。然后主线程调用threadTwo.join()方法后再次被阻塞,等待threadTwo执行完毕后返回。

yield()让出CPU执行权

Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际上就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示

当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程。

线程中断

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止线程的执行,而是被中断的线程根据中断状态自行处理。

  • void interrupt(): 中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true 并立即返回。线程A实际并没有被中断,他会继续往下执行。如果线程A因为调用了wait、join、sleep方法而被阻塞挂起,这个时候如果线程B调用线程A的interrupt方法,线程A会在调用这些方法的地方抛出InterruptedException异常而返回。

  • boolean isInterrupted()方法: 检测当前线程是否被中断,是返回true。

  • boolean interrupted() 方法检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted方法不同,该方法如果发现当前线程被中断,则会清除中断标志。

示例,当线程为了等待一些特定条件的到来时,一般会调用sleep函数、wait函数或者join函数来阻塞挂起当前线程。比如一个线程调用了Threa.sleep(3000), 那么调用线程会被阻塞3s后才会从阻塞状态变为激活状态。但是有时候,如果一直等到3s后再返回有点浪费时间。这时候可以 调用线程的interrupt()方法,强制sleep方法抛出InterruptedException异常而返回。线程恢复到活跃状态。

线程上下文切换

在多线程编程中,线程个数一般大于CPU个数,而每个CPU同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的,CPU资源的分配采用了时间片轮转的策略,也就是给每个线程分配一个时间片,线程在时间片内占用CPU执行任务。当前线程使用完时间片后,就会处于就绪状态并让出CPU给其他线程占用,这就是上下文切换。

从当前线程的上下文切换到其他线程,被切换到的线程需要知道上次CPU运行到了哪里,所以在切换线程的时候要保存好上下文。当再次执行时根据保存的现场信息恢复执行。

守护线程和用户线程

Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实,在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。

守护线程和用户线程有什么区别:区别之一是当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程,并不影响JVM的退出。

如何创建一个守护线程,代码如下

   Thread thread = new Thread(() -> {});//设置为守护线程thread.setDaemon(true);thread.start();

只需要设置线程的dameon参数为true即可。

对比实验

例一:

    public static void main(String[] args) {new Thread(() -> {for (; ; ) {}}).start();System.out.println("main Thread is over");}

结果表明,main线程运行结束,JVM进程并未退出,说明 当父线程结束后,子线程还是可以继续存在的,也就是子线程的生命周期并不受父线程的影响。也说明了在用户线程还存在的情况下JVM进程并不会终止。

例二:

    public static void main(String[] args) {Thread thread1 =  new Thread(() -> {for (; ; ) {}});thread1.setDameon(true);thread1.start();System.out.println("main Thread is over");}

修改以后,发现当main线程运行结束以后,JVM进程也已经终止了,在这个例子中,main函数是唯一的用户线程,thread线程是守护线程,当main线程运行结束后,JVM发现当前已经没有用户线程了,就会终止JVM进程。由于这里的守护线程执行的时候是一个死循环,这也说明了如果当前进程中不存在用户线程,但是还存在正在执行任务的守护线程,则 JVM不等守护线程运行完毕就结束JVM进程

总结:非守护线程(也称为用户线程)都结束了,JVM就会终止,此时所有守护线程会被强制终止,它们正在执行的任务也会随之中断

main线程运行结束后,JVM会自动启动一个叫做DestroyJavaVM的线程,该线程会等待所有用户线程结束后终止JVM进程。JVM代码如下:

    int JNICALL JavaMain(void * args){//执行 Java中的 main函数(*env) ->CallStaticVoidMethod(env, mainClass, mainID, mainArgs) ; //main函数返回值ret = (*env)->ExceptionOccured(env) == NULL ?0 :1;//等待所有非守护线程结束, 然后销毁JVM进程LEAVE();}

LEAVE()是C语言中的一个宏定义,作用是创建一个名为DestroyJavaVM的线程来等待所有用户线程执行结束。

总结:如果希望在主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程,如果希望在主线程结束后子线程继续工作,等子线程结束后再让JVM进程结束,那么就将子线程设置为用户线程。

补充:执行kill 信号,杀死一个应用程序时,如果应用程序仍然有未完成的任务,可以通过添加钩子函数的方式使其可以继续处理并优雅退出

public class App {public static void main(String[] args) {// 添加一个关闭钩子Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("正在执行清理操作...");// 这里放置清理资源的代码,例如关闭文件、数据库连接等performCleanup();System.out.println("清理完成。");}));System.out.println("应用程序正在运行...");// 模拟应用程序运行try {Thread.sleep(10000); // 假设应用运行了一段时间} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("线程被中断。");}}private static void performCleanup() {// 执行实际的清理操作,比如关闭资源等System.out.println("执行资源关闭等清理操作。");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/29841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql数据库迁移步骤

备份数据库: mysqldump -u [username] -p[password] [database_name] > [database_name].sql 注意:“-u”与用户名之间有一个空格,而“-p”与密码之间没有空格 恢复数据库: mysql -u [username] -p[password] [database_name]…

调度算法-内存页面置换算法

缺⻚异常(缺⻚中断) 与⼀般中断的主要区别在于: 缺⻚中断在指令执⾏「期间」产⽣和处理中断信号,⽽⼀般中断在⼀条指令执⾏「完成」后检查和处理中断信号。缺⻚中断返回到该指令的开始重新执⾏「该指令」,⽽⼀般中断返…

【HarmonyOS】鸿蒙应用模块化实现

【HarmonyOS】鸿蒙应用模块化实现 一、Module的概念 Module是HarmonyOS应用的基本功能单元,包含了源代码、资源文件、第三方库及应用清单文件,每一个Module都可以独立进行编译和运行。一个HarmonyOS应用通常会包含一个或多个Module,因此&am…

简单处理字符串——6.14山大软院项目实训1

对于直接输出服务器返回的json到Debug,发现他还包含json的结构,但是不想调试json的返回结构,可以使用简单地处理字符串的方法,而不引入额外的库或复杂的JSON解析,但是这个解决方式是暂时的是投机取巧的,正确…

我主编的电子技术实验手册(08)——串联电阻分压

本专栏是笔者主编教材(图0所示)的电子版,依托简易的元器件和仪表安排了30多个实验,主要面向经费不太充足的中高职院校。每个实验都安排了必不可少的【预习知识】,精心设计的【实验步骤】,全面丰富的【思考习…

单例及工厂模式适合的场景

工厂模式适合以下场景: 1. **对象的创建与使用分离**:工厂模式可以将对象的创建和使用分离,客户端只需要通过工厂来创建对象,而无需关心对象的具体实现细节。 2. **对象的类型不容易预先确定**:当需要根据条件动态创…

Golang——gRPC认证和拦截器

一. OpenSSL 1.1 介绍 OpenSSL是一个开放源代码的软件库包,用于支持网络通讯过程中的加密。这个库提供的功能包含了SSL和TLS协议的实现,并可用于生成密钥、证书、进行密码运算等。 其组成主要包括一下三个组件: openssl:多用途的命…

有效招聘营销策略的六个组成部分

任何想吸引更多人购买其产品的公司都必须投资于市场营销。然而,当涉及到让更多的人了解公司的工作时,许多有效的营销活动可能不是招聘团队的首要考虑因素。为了超越招聘委员会上的“发布祈祷”策略,有必要包括有效招聘营销策略的所有组成部分…

车联网车载设备

智能网联主要通过OBU(On Board Unit,车载单元)实现。OBU是一种安装在车辆上用于实现V2X通信的硬件设备,可实现和其他车辆OBU(PC5)、路侧RSU(PC5)、行人(PC5)和V2X平台&am…

基于Redis实现共享session登录

搭配食用:Redis(基础篇)-CSDN博客 项目实现前的 Mysql中的表: 表说明tb_user用户表tb_user_info用户详情表tb_shop商户信息表tb_shop_type商户类型表tb_blog用户日记表(达人探店日记)tb_follow用户关注表tb_voucher优…

vlcplayer for android 源码编译log打印

vlcplayer for android 源码编译log打印 这篇文章记录了vlcplayer for android 开源库中libvlc.so中添加log打印的方法。 主要针对libvlc源码中msg_Info/msg_Err/msg_Warn/msg_Dbg 函数打印输出到Android log中。修改如下: vlc-android/libvlcjni/vlc/include/vlc…

c++编写自己的assert断言

文章目录 前言实现 前言 在 c c c中&#xff0c;assert只在debug模式下起作用&#xff0c;为了在release下也使用&#xff0c;我们可以实现自己的assert 实现 #include<iostream> #include<cstdlib>bool myAssert(bool expr, const char* file, const char* f…

if/case条件测试语句

一 条件测试 1.1返回码 $? $? 返回码 用来哦按段命令或者脚本是否执行成功 0 true为真就是成功成立 非0 false 失败或者异常 1.2 test 命令 可以进行条件测试 然后根据返回值来判断条件是否成立 -e &#xff1a;exist 测试目录或者目录是否存在 -d : director…

多目标跟踪 (MOT) 算法简介

据说即将开始的欧洲杯将会采用VAR来辅助裁判执法&#xff0c;这无疑将成为本届赛事的一大亮点。VAR&#xff0c;即视频助理裁判&#xff0c;是指在足球比赛中&#xff0c;裁判可以通过视频回放来辅助做出判罚。自2017年国际足联正式将VAR引入足球比赛以来&#xff0c;它已经在世…

【产品经理】订单处理6-审单方案

电商系统中订单管理员会对特殊类型的订单进行审核&#xff0c;普通订单则自动审核&#xff0c;本节讲述自动审单方案、手动审单以及加急审单。 一、自动审单 自动审单方案可按照方案形式制定&#xff0c;可一次性制定多套审单方案。 1. 审单通过条件有 执行店铺&#xff…

同三维T80006EHL-4K30CN 单路4K30 HDMI编码器(全国产化)

同三维T80006EHL-4K30CN 单路4K30 HDMI编码器 带1路HDMI环出和1路3.5音频输入&#xff0c;支持4K30&#xff0c;所有元器件全国产 一、 产品简介&#xff1a; T80006EHL-4K30CN 4K编码器&#xff08;采集盒&#xff09;是一款全国产化的专业4K HDMI音视频编码产品&#xff0c;…

2024年先进机械电子、电气工程与自动化国际学术会议(ICAMEEA 2024)

2024年先进机械电子、电气工程与自动化国际学术会议(ICAMEEA 2024) 2024 International Conference on Advanced Mechatronic, Electrical Engineering and Automation 会议地点&#xff1a;杭州&#xff0c;中国 网址&#xff1a;www.icameea.com 邮箱: icameeasub-conf.c…

大型ERP设计-业务与功能指引:外币折算与辅助账套

外币折算与辅助账套 前言&#xff1a;在对ORACLE和SAP的核心模块功能全面解读的基础上&#xff0c;给出大型ERP设计的建议-业务与功能指引&#xff0c;企业选型、开发大型ERP软件的公司和ERP顾问可以参考。模块包括财务、计划与制造、供应链、项目及设备(MRO)&#xff0c;初步预…

5.How Fast Should You Be When Learning?(你应该用多快的速度学习?)

Normally when I talk about learing quickly, I’m using speed as a synonym for efficiency.Use more effective methods and you’ll learn more in less time.All else being equal, that means you’re learing faster. 通常我在谈到快速学习时&#xff0c;是把“速度&qu…

【HarmonyOS NEXT 】鸿蒙detectBarcode (图像识码)

本模块提供本地图片识码和图像数据识码能力&#xff0c;支持对图像中的条形码、二维码、多功能码进行识别&#xff0c;并获得码类型、码值、码位置信息。 起始版本&#xff1a;4.1.0(11) 导入模块 import { detectBarcode } from kit.ScanKit; InputImage 待识别的图片信息…