Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析
- 一、Runnable接口概述
- 1.1 接口定义
- 1.2 与Thread类的关系
- 1.3 使用Runnable接口的优势
- 二、Runnable接口的基本实现方式
- 2.1 传统方式实现Runnable接口
- 2.2 使用匿名内部类实现Runnable接口
- 2.3 使用Lambda表达式实现Runnable接口
- 三、Runnable接口的高级应用
- 3.1 线程间资源共享
- 3.2 与线程池结合使用
- 3.3 实现带返回值的任务(结合Future)
- 四、Runnable接口与Thread类的对比
- 4.1 主要区别
- 4.2 如何选择
- 五、Runnable接口的实战案例
- 5.1 多线程下载器
- 5.2 定时任务执行器
- 5.3 生产者-消费者模型
- 六、Runnable接口的注意事项
- 6.1 线程安全问题
- 6.2 异常处理
- 6.3 线程中断
- 6.4 资源管理
- 总结
Java除了可以继承Thread
类来创建和管理线程,还可以通过实现Runnable
接口来实现多线程。本文我将详细介绍Runnable
接口的原理、实现方式、高级应用以及与Thread
类的对比,并通过多个实战案例展示其在实际开发中的应用场景,帮你全面掌握Runnable
接口的使用。
一、Runnable接口概述
1.1 接口定义
Runnable
是Java中的一个函数式接口,位于java.lang
包下,其定义如下:
@FunctionalInterface
public interface Runnable {public abstract void run();
}
该接口仅包含一个抽象方法run()
,用于定义线程的执行逻辑。由于是函数式接口,因此可以使用Lambda表达式来简化实现。
1.2 与Thread类的关系
虽然Thread
类是Java中线程的核心类,但通过实现Runnable
接口来创建线程是更推荐的方式。Thread
类本身也实现了Runnable
接口,其构造函数可以接受一个Runnable
对象作为参数,从而将线程的创建和任务的定义分离。
1.3 使用Runnable接口的优势
- 避免单继承限制:Java不支持多重继承,实现
Runnable
接口的类还可以继承其他类 - 更灵活的资源共享:多个线程可以共享同一个
Runnable
实例,便于实现资源共享 - 代码解耦:将线程的创建和任务逻辑分离,提高代码的可维护性和可测试性
- 更好的扩展性:可以与线程池等高级API配合使用
二、Runnable接口的基本实现方式
2.1 传统方式实现Runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "执行: " + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class RunnableExample {public static void main(String[] args) {// 创建Runnable实例MyRunnable myRunnable = new MyRunnable();// 创建并启动线程Thread thread1 = new Thread(myRunnable, "线程1");Thread thread2 = new Thread(myRunnable, "线程2");thread1.start();thread2.start();}
}
2.2 使用匿名内部类实现Runnable接口
public class AnonymousRunnableExample {public static void main(String[] args) {// 使用匿名内部类创建Runnable实例Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "执行: " + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}};// 创建并启动线程Thread thread = new Thread(runnable, "匿名线程");thread.start();}
}
2.3 使用Lambda表达式实现Runnable接口
public class LambdaRunnableExample {public static void main(String[] args) {// 使用Lambda表达式创建Runnable实例Runnable runnable = () -> {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "执行: " + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}};// 创建并启动线程Thread thread = new Thread(runnable, "Lambda线程");thread.start();// 更简洁的写法new Thread(() -> {System.out.println("极简线程执行");}, "极简线程").start();}
}
三、Runnable接口的高级应用
3.1 线程间资源共享
通过实现Runnable
接口,可以轻松实现多个线程共享同一个资源:
class SharedResource implements Runnable {private int count = 0;@Overridepublic void run() {for (int i = 0; i < 1000; i++) {// 同步方法保证线程安全increment();}System.out.println(Thread.currentThread().getName() + "执行完毕,count=" + count);}public synchronized void increment() {count++;}
}public class ResourceSharingExample {public static void main(String[] args) throws InterruptedException {// 创建共享资源实例SharedResource sharedResource = new SharedResource();// 创建并启动多个线程共享同一个资源Thread thread1 = new Thread(sharedResource, "线程1");Thread thread2 = new Thread(sharedResource, "线程2");thread1.start();thread2.start();// 等待两个线程执行完毕thread1.join();thread2.join();System.out.println("最终count值: " + sharedResource.count);}
}
3.2 与线程池结合使用
Runnable
接口是线程池(ExecutorService
)的主要任务类型:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {// 创建固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交多个Runnable任务到线程池for (int i = 0; i < 10; i++) {final int taskId = i;executor.submit(() -> {System.out.println("线程池中的线程执行任务: " + taskId);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executor.shutdown();}
}
3.3 实现带返回值的任务(结合Future)
虽然Runnable
接口的run()
方法没有返回值,但可以通过Future
和Callable
接口实现带返回值的任务:
import java.util.concurrent.*;public class FutureExample {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executor = Executors.newSingleThreadExecutor();// 创建一个Callable任务Callable<Integer> callable = () -> {Thread.sleep(2000);return 100;};// 提交任务并获取FutureFuture<Integer> future = executor.submit(callable);// 获取任务结果(会阻塞直到任务完成)System.out.println("任务结果: " + future.get());executor.shutdown();}
}
四、Runnable接口与Thread类的对比
4.1 主要区别
特性 | Runnable接口 | Thread类 |
---|---|---|
实现方式 | 实现Runnable接口 | 继承Thread类 |
单继承限制 | 无,可以继承其他类 | 受Java单继承限制 |
资源共享 | 天然支持,多个线程可共享同一个Runnable实例 | 需通过静态变量等方式实现资源共享 |
代码结构 | 任务逻辑与线程创建分离,解耦性好 | 任务逻辑与线程创建耦合在一起 |
扩展性 | 可与线程池等高级API更好配合 | 直接使用,扩展性较差 |
4.2 如何选择
- 推荐使用Runnable接口:在大多数情况下,实现
Runnable
接口是更好的选择,尤其是需要资源共享或与线程池配合使用时 - 使用Thread类的场景:当需要重写
Thread
类的其他方法(如start()
、interrupt()
等)时,可以考虑继承Thread
类,但这种场景非常少见
五、Runnable接口的实战案例
5.1 多线程下载器
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;class DownloadTask implements Runnable {private String url;private String outputFile;public DownloadTask(String url, String outputFile) {this.url = url;this.outputFile = outputFile;}@Overridepublic void run() {try (InputStream in = new URL(url).openStream();OutputStream out = new FileOutputStream(outputFile)) {byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = in.read(buffer)) != -1) {out.write(buffer, 0, bytesRead);}System.out.println("下载完成: " + outputFile);} catch (IOException e) {System.err.println("下载失败: " + outputFile + ", 错误: " + e.getMessage());}}
}public class MultiThreadDownloader {public static void main(String[] args) {String[] urls = {"https://example.com/file1.txt","https://example.com/file2.txt","https://example.com/file3.txt"};String[] outputFiles = {"downloads/file1.txt","downloads/file2.txt","downloads/file3.txt"};// 创建并启动多个下载线程for (int i = 0; i < urls.length; i++) {Thread thread = new Thread(new DownloadTask(urls[i], outputFiles[i]));thread.start();}}
}
5.2 定时任务执行器
import java.util.Date;class ScheduledTask implements Runnable {private String taskName;public ScheduledTask(String taskName) {this.taskName = taskName;}@Overridepublic void run() {System.out.println(new Date() + " - 执行任务: " + taskName);try {// 模拟任务执行时间Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(new Date() + " - 任务" + taskName + "执行完毕");}
}public class ScheduledTaskExecutor {public static void main(String[] args) {// 创建并启动定时任务线程Thread task1 = new Thread(new ScheduledTask("数据库备份"));Thread task2 = new Thread(new ScheduledTask("日志清理"));// 设置任务执行间隔Thread scheduler1 = new Thread(() -> {while (true) {task1.run();try {// 每天执行一次Thread.sleep(24 * 60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread scheduler2 = new Thread(() -> {while (true) {task2.run();try {// 每周执行一次Thread.sleep(7 * 24 * 60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}});scheduler1.start();scheduler2.start();}
}
5.3 生产者-消费者模型
import java.util.LinkedList;
import java.util.Queue;class SharedQueue {private Queue<Integer> queue = new LinkedList<>();private static final int MAX_SIZE = 5;public synchronized void produce(int item) throws InterruptedException {// 队列满时等待while (queue.size() == MAX_SIZE) {wait();}queue.add(item);System.out.println("生产者生产: " + item);// 通知消费者notifyAll();}public synchronized int consume() throws InterruptedException {// 队列空时等待while (queue.isEmpty()) {wait();}int item = queue.poll();System.out.println("消费者消费: " + item);// 通知生产者notifyAll();return item;}
}class Producer implements Runnable {private SharedQueue queue;public Producer(SharedQueue queue) {this.queue = queue;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {queue.produce(i);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}class Consumer implements Runnable {private SharedQueue queue;public Consumer(SharedQueue queue) {this.queue = queue;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {queue.consume();Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ProducerConsumerExample {public static void main(String[] args) {SharedQueue queue = new SharedQueue();// 创建生产者和消费者线程Thread producerThread = new Thread(new Producer(queue));Thread consumerThread = new Thread(new Consumer(queue));// 启动线程producerThread.start();consumerThread.start();}
}
六、Runnable接口的注意事项
6.1 线程安全问题
当多个线程共享同一个Runnable
实例时,需要特别注意线程安全问题。可以使用synchronized
关键字、Lock
接口或原子类(如AtomicInteger
)来保证线程安全。
6.2 异常处理
Runnable
接口的run()
方法不允许抛出受检异常,因此需要在方法内部进行异常处理。如果需要处理异常并返回结果,可以考虑使用Callable
接口。
6.3 线程中断
在Runnable
实现中,应该正确处理线程中断请求。可以通过检查Thread.interrupted()
状态或捕获InterruptedException
来实现:
@Override
public void run() {while (!Thread.interrupted()) {// 线程执行逻辑try {Thread.sleep(100);} catch (InterruptedException e) {// 恢复中断状态Thread.currentThread().interrupt();break;}}
}
6.4 资源管理
确保在Runnable
任务中正确管理资源,如文件句柄、网络连接等。可以使用try-with-resources
语句来自动关闭资源。
总结
Runnable
接口是Java多线程编程的重要组成部分,通过实现该接口可以灵活地定义线程任务,并与Java的线程管理机制无缝结合。与继承Thread
类相比,实现Runnable
接口具有更好的扩展性和资源共享能力,是更推荐的多线程实现方式。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ
相关文章:
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...

李沐--动手学深度学习--GRU
1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...
raid存储技术
1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划,涵盖存储系统的布局、数据存储策略等,它明确数据如何存储、管理与访问,为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...
LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考
目录 lua脚本 记录流水 记录流水的作用 流水什么时候删除 我们在做库存扣减的时候,显示基于Lua脚本和Redis实现的预扣减 这样可以在秒杀扣减的时候保证操作的原子性和高效性 lua脚本 // ... 已有代码 ...Overridepublic InventoryResponse decrease(Inventor…...

EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势
一、WebRTC与智能硬件整合趋势 随着物联网和实时通信需求的爆发式增长,WebRTC作为开源实时通信技术,为浏览器与移动应用提供免插件的音视频通信能力,在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能,对实时…...

【版本控制】GitHub Desktop 入门教程与开源协作全流程解析
目录 0 引言1 GitHub Desktop 入门教程1.1 安装与基础配置1.2 核心功能使用指南仓库管理日常开发流程分支管理 2 GitHub 开源协作流程详解2.1 Fork & Pull Request 模型2.2 完整协作流程步骤步骤 1: Fork(创建个人副本)步骤 2: Clone(克隆…...
shell脚本质数判断
shell脚本质数判断 shell输入一个正整数,判断是否为质数(素数)shell求1-100内的质数shell求给定数组输出其中的质数 shell输入一个正整数,判断是否为质数(素数) 思路: 1:1 2:1 2 3:1 2 3 4:1 2 3 4 5:1 2 3 4 5-------> 3:2 4:2 3 5:2 3…...
Android屏幕刷新率与FPS(Frames Per Second) 120hz
Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数,单位是赫兹(Hz)。 60Hz 屏幕:每秒刷新 60 次,每次刷新间隔约 16.67ms 90Hz 屏幕:每秒刷新 90 次,…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
使用 ROS1-Noetic 和 mavros v1.20.1, 携带经纬度海拔的话题主要有三个: /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码,来分析他们的发布过程。发现前两个话题都对应了同一…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...

ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...
算法250609 高精度
加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...

【Java多线程从青铜到王者】单例设计模式(八)
wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒…...
【java】【服务器】线程上下文丢失 是指什么
目录 ■前言 ■正文开始 线程上下文的核心组成部分 为什么会出现上下文丢失? 直观示例说明 为什么上下文如此重要? 解决上下文丢失的关键 总结 ■如果我想在servlet中使用线程,代码应该如何实现 推荐方案:使用 ManagedE…...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...

react菜单,动态绑定点击事件,菜单分离出去单独的js文件,Ant框架
1、菜单文件treeTop.js // 顶部菜单 import { AppstoreOutlined, SettingOutlined } from ant-design/icons; // 定义菜单项数据 const treeTop [{label: Docker管理,key: 1,icon: <AppstoreOutlined />,url:"/docker/index"},{label: 权限管理,key: 2,icon:…...
GeoServer发布PostgreSQL图层后WFS查询无主键字段
在使用 GeoServer(版本 2.22.2) 发布 PostgreSQL(PostGIS)中的表为地图服务时,常常会遇到一个小问题: WFS 查询中,主键字段(如 id)莫名其妙地消失了! 即使你在…...

VSCode 使用CMake 构建 Qt 5 窗口程序
首先,目录结构如下图: 运行效果: cmake -B build cmake --build build 运行: windeployqt.exe F:\testQt5\build\Debug\app.exe main.cpp #include "mainwindow.h"#include <QAppli...

Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目
应用场景: 1、常规某个机器被钓鱼后门攻击后,我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后,我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...

Qwen系列之Qwen3解读:最强开源模型的细节拆解
文章目录 1.1分钟快览2.模型架构2.1.Dense模型2.2.MoE模型 3.预训练阶段3.1.数据3.2.训练3.3.评估 4.后训练阶段S1: 长链思维冷启动S2: 推理强化学习S3: 思考模式融合S4: 通用强化学习 5.全家桶中的小模型训练评估评估数据集评估细节评估效果弱智评估和民间Arena 分析展望 如果…...
虚幻基础:角色旋转
能帮到你的话,就给个赞吧 😘 文章目录 移动组件使用控制器所需旋转:组件 使用 控制器旋转将旋转朝向运动:组件 使用 移动方向旋转 控制器旋转和移动旋转 缺点移动旋转:必须移动才能旋转,不移动不旋转控制器…...

RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上
一、软件介绍 文末提供程序和源码下载 RushDB 改变了您处理图形数据的方式 — 不需要 Schema,不需要复杂的查询,只需推送数据即可。 二、Key Features ✨ 主要特点 Instant Setup: Be productive in seconds, not days 即时设置 :在几秒钟…...
StarRocks 全面向量化执行引擎深度解析
StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计,相比传统行式处理引擎(如MySQL),性能可提升 5-10倍。以下是分层拆解: 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...
SQL进阶之旅 Day 22:批处理与游标优化
【SQL进阶之旅 Day 22】批处理与游标优化 文章简述(300字左右) 在数据库开发中,面对大量数据的处理任务时,单条SQL语句往往无法满足性能需求。本篇文章聚焦“批处理与游标优化”,深入探讨如何通过批量操作和游标技术提…...
深度解析云存储:概念、架构与应用实践
在数据爆炸式增长的时代,传统本地存储因容量限制、管理复杂等问题,已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性,成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理,云存储正重塑数据存储与…...
stm32进入Infinite_Loop原因(因为有系统中断函数未自定义实现)
这是系统中断服务程序的默认处理汇编函数,如果我们没有定义实现某个中断函数,那么当stm32产生了该中断时,就会默认跑这里来了,所以我们打开了什么中断,一定要记得实现对应的系统中断函数,否则会进来一直循环…...
C++ 类基础:封装、继承、多态与多线程模板实现
前言 C 是一门强大的面向对象编程语言,而类(Class)作为其核心特性之一,是理解和使用 C 的关键。本文将深入探讨 C 类的基本特性,包括封装、继承和多态,同时讨论类中的权限控制,并展示如何使用类…...

表单设计器拖拽对象时添加属性
背景:因为项目需要。自写设计器。遇到的坑在此记录 使用的拖拽组件时vuedraggable。下面放上局部示例截图。 坑1。draggable标签在拖拽时可以获取到被拖拽的对象属性定义 要使用 :clone, 而不是clone。我想应该是因为draggable标签比较特。另外在使用**:clone时要将…...
简单介绍C++中 string与wstring
在C中,string和wstring是两种用于处理不同字符编码的字符串类型,分别基于char和wchar_t字符类型。以下是它们的详细说明和对比: 1. 基础定义 string 类型:std::string 字符类型:char(通常为8位)…...