30天学Java第九天——线程

并行与并发的区别

  • 并行是多核 CPU 上的多任务处理,多个任务在同一时间真正的同时执行
  • 并发是单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行,用于解决 IO 密集型任务的瓶颈

线程的创建方式

Thread 的构造方法,可以在创建线程的时候为线程起名字
在这里插入图片描述

  1. 第一种方法:继承 Thread 类
    • 第一步:编写一个类继承 Thread
    • 第二步:重写 run 方法
    • 第三步:new 线程对象
    • 第四步:调用线程对象的 start 方法,启动线程
      一定调用的是 start 方法,不是 run 方法。start 方法的作用就是启动一个线程,线程启动完成该方法就结束。
public class MyThread {public static void main(String[] args) {NewThread nt = new NewThread();// 启动 start 方法启动线程,而不是 run 方法nt.start();for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}class NewThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}
对比一下 run 方法与 start 方法的内存图

调用 run 方法的内存图
调用 run 方法并没有启动新线程,代码都是在 main 方法中执行的,内存只有一个主线程的栈,因此必须 run 方法中的代码执行玩,才能执行后续的代码
在这里插入图片描述
调用 start 方法的内存图
调用 start 方法会启动一个新线程,内存会分配一个新的栈空间给新线程(分配完成start方法就结束了,main 方法中的代码继续向下执行),新线程的代码在新的栈空间执行,main 方法的代码在 main 方法的栈空间执行,两个线程抢夺 CPU 时间片交替执行。
在这里插入图片描述
2. 第二种方法:实现 Runnable 接口

  • 第一步:编写一个类实现 Runnable 接口
  • 第二步:实现接口中的 run 方法
  • 第三步:new 线程对象
    使用Thread的带有 Runnable 参数的构造方法创建对象
    在这里插入图片描述
  • 第四步:调用线程对象的 start 方法,启动线程

推荐使用这种而不是第一种,因为第一种方式使用继承,而Java只能单继承,因此失去了继承其他类的能力,而 实现 Runnable 接口则还能继承其他类

  1. 实现 Callable 接口
  • 第一步:编写一个类实现 Callable 接口
  • 第二步:实现接口中的 run 方法
  • 第三步:new 线程对象
  • 第四步:调用线程对象的 start 方法,启动线程

线程常用的三个方法

  1. final String getName():返回此线程的名称。
  2. final void setName(String name):将此线程的名称更改为等于参数 name 。
  3. static Thread currentThread():返回对当前正在执行的线程对象的引用。

线程七个生命周期

  1. NEW:新建状态
    当线程被创建但尚未开始运行时,它处于新建状态。在这个阶段,线程对象被实例化,但尚未调用 start() 方法。
  2. RUNNABLE:可运行状态
    • 就绪状态
      一旦调用了 start() 方法,线程进入就绪状态。在这个状态下,线程准备好运行,并等待线程调度程序的分配。此状态并不一定代表线程正在运行,可能会处于等待获取CPU时间片的状态。
    • 运行状态
      线程获得CPU资源并开始执行其任务时,线程进入运行状态。在这个状态下,线程执行其代码。
  3. BLOCKED:阻塞状态
    当线程尝试获取一个已经被其他线程持有的锁时,它会进入阻塞状态。在这个状态下,线程无法继续执行,直到它获得所需的锁。
  4. WAITING:等待状态
    如果线程调用 wait()、join() 或 LockSupport.park() 方法,它会进入等待状态。在这种状态下,线程会等待其他线程通知或唤醒它。
  5. TIMED_WAITING:超时等待状态
    线程在等待的同时设置了时间限制(例如,调用 sleep(milliseconds) 或 wait(milliseconds)),将进入超时等待状态。如果在超时时间到达之前线程未被唤醒,则该线程会返回到就绪状态。
  6. TERMINATED:终止/死亡状态
    当线程的 run() 方法执行完毕或者因异常终止时,线程进入终止状态。在这个状态下,线程完成了它的生命周期,无法重新启动。

线程常用的调度方法

  1. start()
    用于启动线程,使其进入就绪状态。
  2. sleep(long millis)
    使当前正在执行的线程暂停指定的时间,被调用的线程会进入阻塞状态,在指定的毫秒数后,线程会回到就绪状态。
  3. yield()
    暂时让出当前线程的执行权,该方法提示线程调度器允许其他同等优先级的线程获得执行时间。
    并不保证在调用后立即释放控制权。
    让位后的线程进入就绪状态,并不会阻塞。
  4. join()
    等待一个线程完成,如果线程 A 调用线程 B 的 join() 方法,线程 A 会阻塞,直到线程 B 执行完毕并终止。
    join(long millis) 方法也可以指定时间,指的是加入 A 线程的时间或者说阻塞 A 线程的时间,时间一到就退出。如果在指定的 millis 时间内,B 线程结束了,被阻塞的 A 线程也会结束阻塞状态。
  5. interrupt()
    发送一个中断信号给线程。如果线程正处于等待、睡眠或阻塞状态,将会抛出InterruptedException。抛出异常就会导致线程退出等待、睡眠或阻塞状态,从而达到中断的目的,利用了异常的机制。
  6. setPriority(int newPriority)
    设置线程的优先级。线程的优先级是一个整数值,范围从1(最低)到10(最高)。这并不保证线程会按优先级执行,但可以指示调度器的优先级。
  7. wait() 和 notify()
    用于线程间的通信和协作。wait() 使线程在对象监视器上等待,直到其他线程调用 notify() 或 notifyAll() 来唤醒它。

如何强制结束一个线程

Thread.stop() 方法可以强制结束线程,但是在 Java1.2 之后就被弃用了,因为使用 stop() 方法会导致线程立即终止,这可能导致锁未解锁、文件未正确关闭等情况,因此强烈不推荐。更好的办法是使用标志位或者 interrupt() 方法。

  1. 设置标志位方法
    通过使用一个共享的标志位来控制线程的生命周期是推荐的做法。主线程可以通过设置标志位来通知子线程应停止执行。
    class CustomThread extends Thread {  private volatile boolean running = true; // 使用 volatile 关键字确保可见性  public void run() {  while (running) {  // 执行任务  System.out.println("Thread is running...");  try {  Thread.sleep(500); // 模拟工作  } catch (InterruptedException e) {  Thread.currentThread().interrupt(); // 恢复中断状态  }  }  System.out.println("Thread is stopping...");  }  public void stopRunning() {  running = false; // 设置标志位  }  
    }  public class Main {  public static void main(String[] args) throws InterruptedException {  CustomThread thread = new CustomThread();  thread.start();  Thread.sleep(2000); // 让线程运行2秒  thread.stopRunning(); // 请求线程停止  thread.join(); // 等待线程结束  System.out.println("Main thread finished.");  }  
    }
    
  2. 使用 interrupt() 方法
    在Java中,interrupt() 方法可以用于中断一个线程。线程在被中断时可以选择捕捉异常或者检查中断状态,从而优雅地结束自己。
    class InterruptibleThread extends Thread {  public void run() {  try {  while (!Thread.currentThread().isInterrupted()) {  // 执行任务  System.out.println("Thread is running...");  Thread.sleep(500); // 模拟工作  }  } catch (InterruptedException e) {  // 捕捉到中断异常,线程可以选择停止  System.out.println("Thread was interrupted.");  Thread.currentThread().interrupt(); // 重新设置中断状态  }  System.out.println("Thread is stopping...");  }  
    }  public class Main {  public static void main(String[] args) throws InterruptedException {  InterruptibleThread thread = new InterruptibleThread();  thread.start();  Thread.sleep(2000); // 让线程运行2秒  thread.interrupt(); // 中断线程  thread.join(); // 等待线程结束  System.out.println("Main thread finished.");  }  
    }
    

守护线程

在 Java 中,线程被分为两大类,一类是用户线程,一类是守护线程。
在 JVM 中,有一个隐藏的守护线程就是 GC 线程

守护线程的特点:

  • 后台运行: 守护线程通常是后台执行的,用于执行一些辅助任务,比如垃圾回收、线程池中的工作线程等。
  • 生命周期受限: JVM 会在所有非守护线程结束后自动结束守护线程。如果没有非守护线程在运行,JVM会退出。
  • 优先级: 守护线程的优先级与普通线程相同,但它们的作用通常是协助非守护线程。

如何创建守护线程

三个步骤创建守护线程:

  1. 创建一个线程实例
  2. 调用 setDaemon(true) 方法,将该线程设置为守护线程
  3. 启动线程
class DaemonThread extends Thread {  @Override  public void run() {  while (true) {  System.out.println("Daemon thread is running...");  try {  Thread.sleep(1000); // 模拟一些工作  } catch (InterruptedException e) {  System.out.println("Daemon thread interrupted.");  }  }  }  
}  public class Main {  public static void main(String[] args) {  Thread daemonThread = new DaemonThread();  daemonThread.setDaemon(true); // 设置为守护线程  daemonThread.start();  try {  Thread.sleep(3000); // 主线程睡眠3秒  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("Main thread is ending...");  // 主线程结束,Daemon线程会随之结束  }  
}

定时任务

在Java中,Timer 是一个用于调度任务的工具类,允许开发者在指定的时间间隔内重复执行任务或在特定的时间点执行任务。Timer 类通常与 TimerTask 类一起使用,其功能足够简单,适合于许多基本的定时任务需求。
Timer: 一个定时器,负责调度任务。
在这里插入图片描述
TimerTask: 一个抽象类,所有需要被调度的任务都需要继承这个类并重写 run() 方法。
在这里插入图片描述

创建定时任务

使用 Timer 和 TimerTask 来创建定时任务的基本步骤如下:

  1. 创建一个 Timer 实例。
  2. 创建一个继承自 TimerTask 的类,并实现 run() 方法。
  3. 使用 Timer 的 schedule() 或 scheduleAtFixedRate() 方法将任务和执行时间关联。

常用方法

  • schedule(TimerTask task, long delay): 在指定的延迟后调度任务。
  • schedule(TimerTask task, Date time): 在指定的时间执行任务。
  • schedule(TimerTask task, long delay, long period): 设定任务在指定的延迟后每隔一个时间段再次执行。
  • scheduleAtFixedRate(TimerTask task, long delay, long period): 类似于 schedule,但是以固定率运行,适用于需要固定间隔执行的任务。
import java.util.Timer;  
import java.util.TimerTask;  public class TimerExample {  public static void main(String[] args) {  Timer timer = new Timer(); // 创建定时器  TimerTask task = new TimerTask() {  @Override  public void run() {  System.out.println("Task executed at: " + System.currentTimeMillis());  }  };  // 在延迟 1 秒后执行任务,每隔 2 秒重复执行  timer.scheduleAtFixedRate(task, 1000, 2000);  // 主线程睡眠 10 秒,以便观察输出  try {  Thread.sleep(10000);  } catch (InterruptedException e) {  e.printStackTrace();  }  // 取消定时器  timer.cancel();  System.out.println("Timer canceled.");  }  
}

注意事项

  • 单线程中: Timer 是单线程的,如果一个 TimerTask 执行时间超过下一个任务的调度时间,后续的任务会被延迟执行。
  • 异常处理: 如果 TimerTask 中的代码抛出未处理的异常,Timer 将停止执行所有后续任务。应确保 run() 方法内的代码是异常安全的。
  • 使用 ScheduledExecutorService: 对于更复杂的定时任务需求(例如线程池,多线程调度等),建议使用 ScheduledExecutorService,它提供了更强大的功能和灵活性。
    import java.util.concurrent.*;  public class ScheduledExecutorExample {  public static void main(String[] args) {  ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);  Runnable task = () -> {  System.out.println("Task executed at: " + System.currentTimeMillis());  };  // 在延迟 1 秒后执行,每隔 2 秒重复执行  scheduler.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);  // 主线程睡眠 10 秒,以便观察输出  try {  Thread.sleep(10000);  } catch (InterruptedException e) {  e.printStackTrace();  }  // 关闭调度器  scheduler.shutdown();  System.out.println("Scheduler shut down.");  }  
    }
    
  • 开发中一般不使用 Timer ,有一些更好的框架可以设置定时任务。

线程优先级

  • 线程是可以设置优先级的,优先级高的,获得CPU时间片的概率会高一些
  • JVM 采用的是抢占式调度模式。谁的优先级高,获取 CPU 的概率就会高
  • 默认情况下,一个线程的优先级是 5
  • 线程优先级最低是 1,最高是 10
  • Thread 类的字段属性
    可以通过 MAX_PRIORITY 和 MIN_PRIORITY,设置最高最低优先级
    在这里插入图片描述

线程安全

什么情况下需要考虑线程安全问题?

  1. 多线程的并发环境下
  2. 有共享的数据
  3. 共享数据涉及到修改操作

一般情况下,局部变量不存在线程安全问题,实例变量和静态变量可能存在线程安全问题。因为局部遍历存储在栈中,实例变量和静态变量存储在堆中,栈每个线程使用自己的,而堆是多线程共享的。

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

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

相关文章

论坛系统(测试报告)

文章目录 一、项目介绍二、设计测试用例三、自动化测试用例的部分展示用户名或密码错误登录成功编辑自己的帖子成功修改个人信息成功回复帖子信息成功 四、性能测试总结 一、项目介绍 本平台是用Java开发&#xff0c;基于SpringBoot、SpringMVC、MyBatis框架搭建的小型论坛系统…

智膳优选 | AI赋能的智慧食堂管理专家 —— 基于飞书多维表格和扣子(Coze)的智能解决方案

智膳优选 | AI赋能的智慧食堂管理专家 基于飞书多维表格和扣子&#xff08;Coze&#xff09;的智能解决方案 数据驱动餐饮管理&#xff0c;让每一餐都是营养与经济的完美平衡&#xff01; “智膳优选”通过整合飞书与Coze&#xff0c;将数据智能引入校园餐饮管理&#xff0…

练习(含指针数组与数组指针的学习)

数组指针是一个指向数组的指针&#xff0c;而指针数组是一个存储指针的数组。 ‌数组指针‌&#xff1a;是一个指针&#xff0c;指向一个数组的首地址&#xff0c;它用于指向整个数组&#xff0c;而不是数组中的某个元素。例如&#xff0c;int (*p)表示 p 是一个指向包含 5 个整…

NSS#Round30 Web

小桃的PHP挑战 <?php include jeer.php; highlight_file(__FILE__); error_reporting(0); $A 0; $B 0; $C 0;//第一关 if (isset($_GET[one])){$str $_GET[str] ?? 0;$add substr($str, 0, 1); $add;if (strlen($add) > 1 ) {$A 1;} else {echo $one; } } else…

MCP基础学习二:MCP服务搭建与配置

文章目录 MCP服务搭建与配置一&#xff0c;学习目标&#xff1a;二&#xff0c;学习内容&#xff1a;1. 如何搭建MCP服务端服务端初始化与配置MCP服务架构与数据流交互图核心实现注册服务功能服务器启动与API暴露 2. 本地应用与MCP服务的集成客户端SDK实现客户端应用实现功能演…

ZKmall开源商城服务端验证:Jakarta Validation 详解

ZKmall开源商城基于Spring Boot 3构建&#xff0c;其服务端数据验证采用Jakarta Validation API​&#xff08;原JSR 380规范&#xff09;&#xff0c;通过声明式注解与自定义扩展机制实现高效、灵活的数据校验体系。以下从技术实现、核心能力、场景优化三个维度展开解析&#…

使用Docker创建postgres

准备工作&#xff1a; 1. 检查网络 检查网络连接&#xff1a;确保你的服务器网络连接正常&#xff0c;可尝试使用 ping 命令测试与 Docker Hub 服务器&#xff08;如 ping registry-1.docker.io&#xff09;的连通性。 ping registry-1.docker.io 检查防火墙&#xff1a;确…

32 python json

在办公室忙碌的日常里,我们经常需要和各种数据打交道。想象一下,你是办公室里负责处理员工信息、项目数据的 “数据小管家”,每天都要面对大量格式各异的数据。 这时候,JSON(JavaScript Object Notation)就像是你得力的数据助手,它是一种轻量级的数据交换格式,简单又高…

Java 实现 List<String> 与 String 互转

在 Java 开发过程中&#xff0c;有时需要将 List<String> 转为 String 存储&#xff0c;后续使用时再还原回去。此时就需要 Java 实现 List<String> 与 String 互转。以下是一种互转方式。 采用如下工具包实现。 <dependency><groupId>org.apache.com…

NO.87十六届蓝桥杯备战|动态规划-完全背包|疯狂的采药|Buying Hay|纪念品(C++)

完全背包 先解决第⼀问 状态表⽰&#xff1a; dp[i][j]表⽰&#xff1a;从前i个物品中挑选&#xff0c;总体积不超过j&#xff0c;所有的选法中&#xff0c;能挑选出来的最⼤价 值。&#xff08;这⾥是和01背包⼀样哒&#xff09; 那我们的最终结果就是dp[n][V] 。状态转移⽅…

第十三天 - Ansible基础架构 - YAML语法与Playbook - 练习:批量配置部署

Ansible自动化运维实战&#xff1a;从入门到批量配置部署 前言&#xff1a;自动化运维的时代选择 在服务器规模呈指数级增长的今天&#xff0c;手工操作已无法满足运维需求。本文将手把手教你使用Ansible这个明星级自动化工具&#xff0c;通过YAML语法和Playbook实现批量配置…

Redis的过期和内存淘汰策略

文章目录 惰性删除定期删除内存满了&#xff0c;数据淘汰策略 Redis 提供了两种删除策略&#xff1a; 惰性删除 、定期删除 惰性删除 定期删除 两种清除模式: 内存满了&#xff0c;数据淘汰策略 Redis 提供了八种数据淘汰策略&#xff1a; 1. 默认是不淘汰任何的 key&#x…

用PHPExcel 封装的导出方法,支持导出无限列

用PHPExcel 封装的导出方法&#xff0c;支持导出无限列 避免PHPExcel_Exception Invalid cell coordinate [1 异常错误 /*** EXCEL导出* param [string] $file_name 保存的文件名及表格工作区名&#xff0c;不加excel后缀名* param [array] $fields 二维数组* param [array] $…

WHAT - React 元素接收的 ref 详解

目录 1. ref 的基本概念2. 如何使用 ref2.1 基本用法2.2 类组件使用 createRef 3. forwardRef 转发 ref4. ref 的应用场景5. ref 和函数组件总结 在 React 中&#xff0c;ref&#xff08;引用&#xff09;用于访问 DOM 元素或类组件实例。它允许我们直接与元素进行交互&#xf…

【QT】QT的消息盒子和对话框(自定义对话框)

QT的消息盒子和对话框&#xff08;自定义对话框&#xff09; 一、消息盒子QMessageBox1、弹出警告盒子示例代码&#xff1a;现象&#xff1a; 2、致命错误盒子示例代码&#xff1a;现象&#xff1a; 3、帮助盒子示例代码&#xff1a;现象&#xff1a; 4、示例代码&#xff1a; …

依靠视频设备轨迹回放平台EasyCVR构建视频监控,为幼教连锁园区安全护航

一、项目背景 幼教行业连锁化发展态势越发明显。在此趋势下&#xff0c;幼儿园管理者对于深入了解园内日常教学与生活情况的需求愈发紧迫&#xff0c;将这些数据作为提升管理水平、优化教育服务的重要依据。同时&#xff0c;安装监控系统不仅有效缓解家长对孩子在校安全与生活…

Stable Diffusion+Pyqt5: 实现图像生成与管理界面(带保存 + 历史记录 + 删除功能)——我的实验记录(结尾附系统效果图)

目录 &#x1f9e0; 前言 &#x1f9fe; 我的需求 &#x1f527; 实现过程&#xff08;按功能一步步来&#xff09; &#x1f6b6;‍♂️ Step 1&#xff1a;基本图像生成界面 &#x1f5c3;️ Step 2&#xff1a;保存图片并显示历史记录 &#x1f4cf; Step 3&#xff1a…

量子计算未来的潜力和挑战

据麦肯锡预测&#xff0c;到 2035 年或 2040 年&#xff0c;量子计算市场规模可能增长至约 800 亿美元。目前&#xff0c;许多量子比特技术正竞相成为首台通用、无差错量子计算机的基础&#xff0c;但仍面临诸多挑战。 我们将探讨量子计算的未来前景、潜力&#xff0c;以及它对…

ArcGIS 给大面内小面字段赋值

文章目录 引言:地理数据处理中的自动化赋值为何重要?实现思路模型实现关键点效果实现步骤1、准备数据2、执行3、完成4、效果引言:地理数据处理中的自动化赋值为何重要? 在地理信息系统(GIS)的日常工作中,空间数据的属性字段赋值是高频且关键的操作,例如在土地利用规划…

如何打通虚拟化-容器环境并保障流量安全?SmartX VCCI 方案升级!

为了提升资源利用率、交付效率和业务灵活性&#xff0c;不少企业用户都在推进从传统架构向云原生架构的演进&#xff0c;并采用虚拟机与容器共存的混合模式支持多种业务系统。由于两个环境在业务交互层面形成高度耦合&#xff0c;企业需要具备简单、高效方案&#xff0c;实现虚…