Java核心知识体系-并发与多线程:线程基础
1 先导
Java线程基础主要包含如下知识点,相信我们再面试的过程中,经常会遇到类似的提问。
1、线程有哪几种状态? 线程之间如何转变?
2、线程有哪几种实现方式? 各优缺点?
3、线程的基本操作(线程管理机制)有哪些?
4、线程如何中断?
5、线程有几种互斥同步方式? 如何选择?
6、线程之间的协作方式(通信和协调)?
下面我们 一 一 解读。
2 线程的状态和流转
2.1 新建(New)
如上图,创建完线程,但尚未启动。
2.2 可运行(Runnable)
如上图,处于可运行阶段,正在运行,或者正在等待 CPU 时间片。包含了 Running
和 Ready
两种线程状态。
2.3 阻塞(Blocking)
如上图,正被Lock住,等待获取一个排它锁,如果其他的线程释放了锁,该状态就会结束。
2.4 无限期等待(Waiting)
如上图,处在无限期等待阶段,等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。主要有两种方式进行释放:
-
调用方的线程执行完成
-
使用 Object.notify() / Object.notifyAll()进行显性唤醒
2.5 限期等待(Timed Waiting)
如上图,因为有时间控制,所以无需等待其它线程显式地唤醒,一定时间之后,系统会自动唤醒。所以他有三种方式进行释放:主要有两种方式进行释放:
-
调用方的线程执行完成
-
使用 Object.notify() / Object.notifyAll()进行显性唤醒
-
时间到结束Thread.sleep()Object.wait() 方法,带Timeout参数Thread.join() 方法,带Timeout参数
2.6 死亡(Terminated)
-
线程结束任务之后结束
-
产生了异常并结束
3 线程实现方式
在Java中,线程的实现方式主要有两种:继承Thread
类和实现Runnable
接口。此外,Java 5开始,引入了java.util.concurrent
包,提供了更多的并发工具,如Callable
接口与Future
接口,它们主要用于任务执行。
3.1 继承Thread类
通过继承Thread
类来创建线程是最基本的方式。你需要创建一个扩展自Thread
类的子类,并重写其run()
方法。然后,可以创建该子类的实例来创建新的线程。
class MyThread extends Thread {public void run() {System.out.println("线程运行中");}
}public class ThreadDemo {public static void main(String[] args) {MyThread t = new MyThread();t.start(); // 调用start()方法来启动线程}
}
3.2 实现Runnable接口
另一种方式是让你的类实现Runnable
接口,并实现run()
方法。然后,你可以创建Thread
类的实例,将实现了Runnable
接口的类的实例作为构造参数传递给它。
class MyRunnable implements Runnable {public void run() {System.out.println("线程运行中");}
}public class RunnableDemo {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start(); // 调用start()方法来启动线程}
}
3.3 使用Callable和Future
虽然Callable
和Future
不是直接用于创建线程的,但它们提供了一种更灵活的方式来处理线程执行的结果。Callable
类似于Runnable
,但它可以返回一个结果,并且可以抛出异常。Future
用于获取Callable
执行的结果。
import java.util.concurrent.*;class MyCallable implements Callable<String> {public String call() throws Exception {return "任务完成";}
}public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executor = Executors.newSingleThreadExecutor();Future<String> future = executor.submit(new MyCallable());System.out.println(future.get()); // 阻塞等待获取结果executor.shutdown();}
}
3.4 优缺点解读
-
继承Thread类:简单直观,但Java不支持多重继承,如果类已经继承了其他类,则不能再用这种方式。另外继承整个 Thread 类开销过大,太重了。
-
实现Runnable接口:更加灵活,推荐的方式。
-
Callable和Future:提供了更为强大的功能,例如返回执行结果和抛出异常,但通常用于与
ExecutorService
等高级并发工具一起使用。
4 线程管理机制
Java 中的线程管理机制非常强大,涵盖了从简单的线程创建到复杂的线程池管理等多个方面。
4.1 Executor 框架
Executor
框架是 Java 并发包(java.util.concurrent
)中的一个关键组件,它提供了一种更高级别的抽象来管理线程池。通过使用 Executor
,你可以更容易地控制线程的创建、执行、调度、生命周期等。它主要有三种类型:
-
CachedThreadPool: 一个任务创建一个线程
-
FixedThreadPool: 所有任务只能使用固定大小的线程
-
SingleThreadExecutor: 单个线程,相当于大小为 1 的 FixedThreadPool。
-
优点:提高程序性能和响应速度,通过复用线程来减少线程创建和销毁的开销,简化并发编程。
-
使用示例:
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {Runnable worker = new WorkerThread("" + i);executor.execute(worker);
}
executor.shutdown();
4.2 守护线程(Daemon Threads)
守护线程是一种特殊的线程,它主要用于程序中“后台”任务的支持。守护线程与普通线程的区别在于,当程序中所有非守护线程结束时,JVM 会自动退出,即使还有守护线程在运行。守护线程常用于垃圾回收、JVM 内部的监控等任务。设置守护线程:通过调用线程对象的 setDaemon(true)
方法,在启动线程之前将其设置为守护线程。
Thread thread = new Thread(new MyRunnable());thread.setDaemon(true);
4.3 sleep() 方法
sleep()
方法是 Thread
类的一个静态方法,用于让当前正在执行的线程暂停执行指定的时间(毫秒),以毫秒为单位。在指定的时间过去后,线程将回到可运行状态,等待CPU的调度。
-
用途:常用于线程间的简单同步。
-
注意:
sleep()
方法不会释放锁(如果当前线程持有锁的话)。 -
示例:
try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}
4.4 yield() 方法
yield()
方法也是 Thread
类的一个静态方法,它告诉调度器当前线程愿意放弃当前处理器的使用,但这并不意味着线程会立即停止执行或进入等待/阻塞状态。调度器可以忽略这个提示,继续让当前线程运行。
-
用途:提示调度器让出CPU时间,但具体是否让出取决于调度器的实现。
-
注意:
yield()
方法不会使线程进入阻塞状态,也不会释放锁(如果持有的话),类似仅建议。 -
示例:
Thread.yield();
5 线程中断方式
在Java中,线程中断是一种重要的线程间通信机制,用于通知线程应该停止当前正在执行的任务。线程中断的方式主要有以下几种:
5.1 使用interrupt()
方法
interrupt()
方法是Java推荐的线程中断方式。它并不会直接停止线程,而是设置线程的中断状态为true。线程需要定期检查这个中断状态(通过isInterrupted()
方法),并根据需要自行决定如何响应中断请求,比如退出循环、释放资源等。
-
优点:安全、灵活,符合Java的并发编程理念。
-
示例
Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {// 执行任务}// 线程中断后的清理工作
});
thread.start();
// 稍后中断线程
thread.interrupt()
5.2 使用Executor
的中断操作
-
调用 Executor 的 shutdown() 方法,会等待线程都执行完毕之后再关闭
-
调用 Executor 的 shutdownNow() 方法,则相当于直接调用具体线程的 interrupt() 方法
6 线程互斥同步方式
Java中的线程互斥同步是并发编程中的一个重要概念,用于保证多个线程在访问共享资源时的互斥性,即同一时间只有一个线程能够访问某个资源。Java提供了多种机制来实现线程的互斥同步,主要包括以下几种方式:
6.1 synchronized关键字
1. 基本概念:
synchronized是Java中最基本的同步机制,它可以用来修饰方法或代码块。当一个线程访问一个被synchronized修饰的方法或代码块时,其他试图访问该方法或代码块的线程将被阻塞,直到当前线程执行完毕释放锁。
2. 使用方法:
-
修饰方法:直接在方法声明上加上synchronized关键字,例如
public synchronized void method() {...}
。 -
修饰代码块:将需要同步的代码放在synchronized(对象) {...}中,这里的对象就是锁对象,例如
synchronized(this) {...}
或synchronized(某个对象) {...}
。
3. 特性:
-
可见性:synchronized不仅保证了互斥性,还保证了变量的可见性。当一个线程释放锁时,会将锁变量的值刷新到主存储器中,从而使其他线程可以看到最新的变量值。
-
可重入性:synchronized支持可重入性,即同一个线程可以多次获取同一个锁,而不会导致死锁。
4. 示例:
public class Counter { private int count = 0; // synchronized修饰方法 public synchronized void increment() { count++; } public synchronized int getCount() { return count; }
} public class TestSynchronized { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 10; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 10; i++) { counter.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Final count: " + counter.getCount()); }
}
6.2 ReentrantLock类
-
基本概念:ReentrantLock是java.util.concurrent.locks包中的一个可重入锁,它提供了比synchronized更灵活的锁定机制。
-
使用方法:创建锁对象:
ReentrantLock lock = new ReentrantLock();
加锁:lock.lock();
释放锁:通常将释放锁的代码放在finally块中,以确保锁一定会被释放,例如try {...} finally { lock.unlock(); }
。
-
特性:支持公平锁和非公平锁:通过构造器参数可以指定使用哪种锁,默认是非公平锁。支持尝试获取锁:提供了
tryLock()
等方法,尝试获取锁,如果获取不到则不会阻塞线程。支持中断锁定的线程:与synchronized不同,ReentrantLock的锁可以被中断。
import java.util.concurrent.locks.ReentrantLock; public class CounterWithLock { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); // 创建ReentrantLock对象 public void increment() { lock.lock(); // 加锁 try { count++; } finally { lock.unlock(); // 释放锁,放在finally块中确保一定会被释放 } } public int getCount() { lock.lock(); // 加锁 try { return count; } finally { lock.unlock(); // 释放锁 } }
} public class TestReentrantLock { public static void main(String[] args) throws InterruptedException { CounterWithLock counter = new CounterWithLock(); Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) { counter.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Final count: " + counter.getCount()); }
}
6.3 对比
对于大多数简单场景,synchronized关键字是最直接、最简单的选择;而对于需要更灵活控制锁的场景,则可以考虑使用ReentrantLock等高级同步机制。
7 线程协作(通信)方案
Java中线程之间的协作主要可以通过多种机制实现,其中等待/通知机制(wait/notify/notifyAll
)和join
方法是两种常用的方式。下面我将分别给出这两种方式的简单代码示例。
7.1 等待/通知机制(wait/notify/notifyAll)
等待/通知机制依赖于Java中的Object
类,因为wait()
, notify()
, 和 notifyAll()
方法都定义在Object
类中。这些方法必须在同步块或同步方法中被调用,因为它们是用来控制对某个对象的访问的。
示例代码:
public class WaitNotifyExample {private final Object lock = new Object();private boolean ready = false;public void doWait() {synchronized (lock) {while (!ready) {try {lock.wait(); // 当前线程等待} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 保持中断状态}}// 当ready为true时,继续执行}}public void doNotify() {synchronized (lock) {ready = true;lock.notify(); // 唤醒在此对象监视器上等待的单个线程// 或者使用 lock.notifyAll(); 唤醒所有等待的线程}}public static void main(String[] args) {WaitNotifyExample example = new WaitNotifyExample();Thread t1 = new Thread(() -> {System.out.println("Thread 1 is waiting");example.doWait();System.out.println("Thread 1 is proceeding");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000); // 假设t2需要一些时间来完成准备工作} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Thread 2 is notifying");example.doNotify();});t1.start();t2.start();}
}
在这个例子中,t1
线程在doWait()
方法中等待,直到t2
线程调用doNotify()
方法并设置ready
为true
。t2
线程模拟了一些准备工作,并在之后唤醒t1
。
7.2 Join 方法
join
方法是Thread
类的一个方法,用于让当前线程等待另一个线程完成其执行。
示例代码:
public class JoinExample {public static void main(String[] args) {Thread t1 = new Thread(() -> {try {Thread.sleep(1000); // 假设t1执行需要一些时间} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Thread 1 completed");});t1.start();try {t1.join(); // 当前线程(main线程)等待t1完成} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Thread 1 has joined, continuing main thread");}
}
在这个例子中,main
线程启动了一个新线程t1
,并通过调用t1.join()
等待t1
完成。t1
线程在完成后会打印一条消息,而main
线程会在t1
完成后继续执行并打印另一条消息。
文章转载自:Hello-Brand
原文链接:https://www.cnblogs.com/wzh2010/p/15886701.html
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:

Java核心知识体系-并发与多线程:线程基础
1 先导 Java线程基础主要包含如下知识点,相信我们再面试的过程中,经常会遇到类似的提问。 1、线程有哪几种状态? 线程之间如何转变? 2、线程有哪几种实现方式? 各优缺点? 3、线程的基本操作(线程管理机制ÿ…...

KRaft模式下的Kafka启动指南:摆脱Zookeeper依赖
一、背景介绍 多年来,人们一直在同时使用Apache ZooKeeper和Apache Kafka。但是自Apache Kafka 3.3发布以来,它就可以在没有ZooKeeper的情况下运行。同时它包含了新的命令kafka-metadata-quorum和kafka-metadata-shell?该如何安装新版kafka,…...

【数据库】MySQL-基础篇-函数
专栏文章索引:数据库 有问题可私聊:QQ:3375119339 目录 一、简介 二、字符串函数 三、数值函数 四、日期函数 五、流程函数 一、简介 函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着,这一段程序或代码在 M…...

dp练习【4】
最长数对链 646. 最长数对链 给你一个由 n 个数对组成的数对数组 pairs ,其中 pairs[i] [lefti, righti] 且 lefti < righti 。 现在,我们定义一种 跟随 关系,当且仅当 b < c 时,数对 p2 [c, d] 才可以跟在 p1 [a, b…...
php 实现推荐算法
在PHP中实现推荐算法的应用场景通常包括电商、社交媒体、内容平台等。推荐算法可以帮助用户找到与其兴趣相关的内容,提高用户体验和平台黏性。以下是几种常见的推荐算法及其PHP实现方式: 1. 基于协同过滤的推荐算法 协同过滤(Collaborative…...
相机光学(三十六)——光圈
0.参考链接 (1)Hall光圈和Piris光圈的区别 (2)自动光圈及P-IRIS原理 1.光圈分类 Hall光圈和Piris光圈是两种不同的光圈技术。它们之间的区别如下: Hall光圈:Hall光圈是一种传统的光电子元件,通…...

数据结构——树和二叉树
目录 一、树的概念 二、树结点之间的关系 三、二叉树 1、满二叉树 2、完全二叉树 四、二叉树的存储 1、顺序存储 2、链式存储 一、树的概念 如果数据和数据之间满足一对多的关系,将其逻辑结构称之为树 如下图:树的根与树的分支存在一对多的关系 将上…...

142. Go操作Kafka(confluent-kafka-go库)
文章目录 Apache kafka简介开始使用Apache Kafka构建生产者构建消费者 总结 之前已经有两篇文章介绍过 Go如何操作 kafka 28.windows安装kafka,Go操作kafka示例(sarama库) 51.Go操作kafka示例(kafka-go库) Apache ka…...

spring boot(学习笔记第十九课)
spring boot(学习笔记第十九课) Spring boot的batch框架,以及Swagger3(OpenAPI)整合 学习内容: Spring boot的batch框架Spring boot的Swagger3(OpenAPI)整合 1. Spring boot batch框架 Spring Batch是什么 Spring Batch 是一个…...
docker安装 redis 并且加密开启SSL/TLS通道
拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/redis:latest docker tag registry.cn-hangzhou.aliyuncs.com/qiluo-images/redis:latest redis:latest要在 Docker 容器中启动 Redis 并开启 SSL/TLS 加密,需按照以下步骤修改启动命令和配置…...

什么是ARM架构?什么是X86架构?两者的区别是什么?
一、什么是ARM架构 (一)起源于发展 ARM 架构由英国剑桥的 Acorn 计算机公司开发。因市场无合适产品,Acorn 自行设计出第一款微处理器,命名为 ARM。此后 ARM 架构不断发展,1990 年为与苹果合作成立 ARM 公司࿰…...

【vscode】vscode paste image插件设置
本文首发于 ❄️慕雪的寒舍 vscode编辑md文件的时候,如果想插入图片,自带的粘贴只会粘贴到当前目录下,也没有文件重命名,很不友好。 在扩展商店里面有mushan的Paste Image插件,相比自带的,更加友好一点。但…...

自定义string类
#include <iostream> #include <string> int main() { std::string str "Hello, World!"; // 使用 c_str() 将 std::string 转换为 C 风格字符串,并传递给 printf printf("The string is: %s\n", str.c_str()); // 尝试修改…...

Python | Leetcode Python题解之第387题字符串中的第一个唯一字符
题目: 题解: class Solution:def firstUniqChar(self, s: str) -> int:position dict()q collections.deque()n len(s)for i, ch in enumerate(s):if ch not in position:position[ch] iq.append((s[i], i))else:position[ch] -1while q and po…...
RocketMQ 消费时序列化报错问题分析及解决
问题背景 在2024年3月7日,系统消费 RocketMQ 消息时出现了序列化报错,错误信息显示为: java.io.InvalidClassException: com.xxx.xxx.bean.mg.GoodsChangeLogMessage; local class incompatible: stream classdesc serialVersionUID... 这是…...

全能与专精:探索未来AI模型的发展趋势与市场潜力
文章目录 每日一句正能量前言AI模型的全面评估和比较AI模型的专精化和可扩展性AI模型的合理使用和道德规范后记 每日一句正能量 一个人,如果没有经受过投资失败的痛楚,又怎么会看到绝望之后的海阔天空。很多时候,经历了人生中最艰难的事&…...
Python深度学习:【开源数据集系列】ImageNet数据集
ImageNet 是一个大规模的视觉数据集,是计算机视觉领域最重要的基准数据集之一。该数据集由普林斯顿大学和斯坦福大学的研究人员发起,于 2009 年推出。ImageNet 是用于物体分类、目标检测、图像分割、姿势估计等多种任务的通用数据集,尤其在深度学习和计算机视觉的突破性研究…...

微信小程序手写签名
微信小程序手写签名组件 该组件基于signature_pad封装,signature_pad本身是web端的插件,此处将插件代码修改为小程序端可用。 signature_pad.js /*!* Signature Pad v5.0.3 | https://github.com/szimek/signature_pad* (c) 2024 Szymon Nowak | Releas…...

Javascript 使用中点查找矩形的角(Find Corners of Rectangle using mid points)
考虑一个矩形 ABCD,我们给出了边 AD 和 BC 中点(分别为 p 和 q)的坐标以及它们的长度 L(AD BC L)。现在给定参数,我们需要打印 4 个点 A、B、C 和 D 的坐标。 例子: 输入:p (1,…...

【困难】 猿人学web第一届 第18题 jsvmp 洞察先机
文章目录 数据接口分析还原加密参数插桩调试分析日志插桩补充 python 代码 数据接口分析 数据接口 https://match.yuanrenxue.cn/match/18data 请求参数 {page: 页码, t: 时间戳, v: 加密值} 请求第一页不需要携带 t, v 参数 cookie 只需要携带 sessionid 只要 还原加密字段…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...