多线程、异步编程、并发读写 新认识
2023/10/9 21:02:59
本文主要是介绍多线程、异步编程、并发读写 新认识,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
大家好,我是大圣,好久不见。
在我们上次的探讨中,我们深入了解了并发、并行和高并发这三个核心概念,它们都在我们的现代计算环境中扮演着关键的角色,使得系统能够更加高效地满足大量的请求。
在这篇文章中,我们将进一步探索多线程和异步编程的魅力,了解并发读写的挑战。
多线程和异步编程
实现高并发的方法有:多线程和多进程、负载均衡、缓存技术、数据库优化、异步处理、分布式系统等。
下面我们来详细说一下多线程和异步编程。
多线程
定义
多线程是一种允许单个程序创建多个并行执行流(线程)的技术。这些线程可以并发执行,每个线程都有自己的一套寄存器、程序计数器和栈,但它们会共享同一进程中的其他资源,如代码、数据和文件。
现代操作系统和多核 CPU 使得真正的线程并行执行成为可能,这意味着多个线程可以在不同的 CPU 核上同时执行。对于单核 CPU,操作系统通过时间片切换技术使得各个线程轮流执行,给人一种“并行”的错觉。
举例理解
上面这种专业的定义,理解起来比较晦涩,大家可以看下面我举得这个例子:
想象一下,你在一条单车道的路上驾驶,这条路代表了单线程。无论有多少车,它们都必须一个接一个地行驶,无法超车或并行行驶。但如果这是一条多车道的高速公路,每条车道都像一个线程,多辆车可以并行行驶,增加了整体的吞吐量。
多线程如何实现高并发?
举例理解
想象一下你进入了一个餐厅。这个餐厅只有一个服务员,不管餐厅里有多少客人,这个服务员都需要一个一个去服务。这种情况下,如果有 10 个客人同时进来,他们必须排队等待。这就像一个单线程的系统,无法处理高并发。
现在,假设餐厅决定雇佣更多的服务员。当 10 个客人同时进入餐厅,有 10 个服务员,每个服务员都可以为一个客人服务,所有的客人都能同时得到服务,没有等待时间。这就是多线程的方式,每个服务员都代表一个线程。
专业解释
在计算机领域,当有大量的用户请求到来时,如果只有一个执行线程,那么请求就需要排队等待。但如果系统采用了多线程技术,每个请求都可以由一个单独的线程来处理,从而实现真正的并行处理,大大提高了系统的并发处理能力。
这就是多线程如何帮助实现高并发的原理。通过多线程,系统能够同时处理多个任务,而不是顺序执行,从而大大提高了整体的执行效率和响应速度。
同样,在 Java 程序中,如果我们面临大量的用户请求或任务,而我们只有一个线程来处理这些请求,那么它们将会一个接一个地被处理。但是,如果我们使用多线程,那么我们可以并行地处理多个请求,大大提高了应对高并发的能力。
Java 代码实现
Java 提供了丰富的库和工具,如 Thread 类、ExecutorService 和 ThreadPoolExecutor 等,来帮助开发者方便地创建和管理线程。如下列方式:
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池 for (int i = 0; i < 10; i++) { Runnable worker = new MyRunnable(); executor.execute(worker); } executor.shutdown(); // 关闭线程池
总的来说,多线程允许程序同时执行多个任务,从而增加了程序的并发处理能力。当面临高并发的情况时,合理地使用多线程可以帮助我们提高系统的响应速度和吞吐量。
但是多线程去实现高并发的话,当多个线程去同时修改一个值的时候,这里就会出现并发写的问题。这个我们在后面会详细说。
异步编程
定义
异步编程是一种程序设计方法,它允许任务能够独立于主程序运行,这意味着您可以继续执行其他任务,而不必等待该任务完成。
举例理解异步编程
考虑一个简单的场景:你有一个应用,需要从数据库中读取数据、从网络上下载文件和写入日志文件。在传统的同步编程中,你可能会先读取数据库,等待数据返回后,再下载文件,下载完成后,再写入日志。但在高并发的环境下,这种等待将造成资源的浪费。
但如果使用异步编程,你可以在启动数据库查询后,立即开始文件下载;在这两个操作在后台运行的同时,你还可以写入日志。所有操作都没有相互阻塞。
异步编程的思想
1)非阻塞: 主线程启动一个异步任务后,不会傻傻地等待,而是可以继续执行其他任务。
2)事件驱动: 当异步任务完成时,会触发一个事件通知主线程。
3)回调函数: 一般与事件配合,异步任务完成后由主线程调用。
异步编程实现高并发
异步编程通过允许任务在后台执行,使得主线程可以处理更多的任务。这样,在高并发的环境下,系统能够更有效地使用资源,处理更多的请求。
Java 代码实现
在 Java 中,CompletableFuture 是处理异步编程的常用工具。下面是一个简单的示例:
import java.util.concurrent.CompletableFuture; public class AsyncExample { public static void main(String[] args) { // 创建一个异步任务 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { try { Thread.sleep(2000); // 模拟一个长时间的操作 System.out.println("异步任务完成"); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println("主线程继续执行"); // 等待异步任务完成 future.join(); System.out.println("主线程结束"); } }
在上述代码中,我们创建了一个异步任务,该任务仅仅是等待 2 秒然后打印出消息。而主线程在启动异步任务后,不必等待它完成,而是继续执行。这是一个简单的示例,但它展示了异步编程如何允许主线程和其他任务并发执行。
并发写与并发读
并发写
定义
并发写(Concurrent Writes)是指在同一时间段内,多个客户端或线程试图同时写入(或更新)数据库或文件系统中的同一个数据项或资源。
为了更好的理解并发写的概念,大家可以看下面例子:
场景 1:没有冲突的并发写
时间点 1:客户端 1 开始编辑文档,他正在编写第 1 段。
时间点 2:客户端 2 开始编辑文档,但他正在编辑第 3 段(不是第 1 段)。
时间点 3:客户端 1 保存他对第 1 段的更改。
时间点 4:客户端 2 保存他对第 3 段的更改。
在这个情况下,即使两个客户端在同一时间段内编辑文档,也没有问题,因为他们编辑的是文档的不同部分,没有产生冲突。
场景 2:产生冲突的并发写
时间点 1:客户端 1 开始编辑文档,他正在编写第 1 段。
时间点 2:客户端 2 也开始编辑文档,他也正在编辑第 1 段。
时间点 3:客户端 1 保存他对第 1 段的更改。
时间点 4:客户端 2 尝试保存他对第 1 段的更改,但系统提示他第 1 段已被更改,让他选择是覆盖更改还是合并更改。
在这个情况下,两个客户端在同一时间段内编辑了文档的同一部分,产生了写冲突。
所以,“同一时间段内的并发写”可能会根据写操作是否影响到同一数据项而导致冲突或不冲突。希望这个例子能帮助你理解“同一时间段”这一概念和并发写可能出现的情况。
小结
在多用户或多线程的环境中,这是很常见的现象,但它也带来了一些问题和挑战,主要包括:
数据一致性问题、竞态条件。
为了解决这些问题和挑战,通常会使用一系列技术和机制来控制并发写,包括:锁机制、事务管理、乐观并发控制等。
具体怎么解决并发写出现冲突的问题,我们在锁和 MVCC 部分会详细说的。
并发读
定义
并发读是指多个客户端或线程在同一时间段内尝试读取数据库、文件系统或其他共享资源中的相同数据或资源。
举例理解
想象一个图书馆的情境:假如有一本非常受欢迎的新书。多个读者(线程)同时想要读这本书(数据)。他们都可以同时坐下来阅读书中的内容,而不会影响到其他读者的阅读体验。
并发读思想
1)无害性:读操作本身不修改数据,所以理论上,多个线程同时读取同一资源不会造成冲突或数据不一致。
2)高效:多个线程可以并发地读取数据,从而提高系统的响应速度和吞吐量。
实现高并发
并发读可以提高系统的并发性,因为:
1)读操作可以被同时发起和执行,不需要等待其他读操作完成。
2)读操作不会阻塞其他操作(除非涉及到某些锁定策略)。
Java 代码实现
以下是一个简单的 Java 示例,展示了使用 ReentrantReadWriteLock 来支持并发读。
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ConcurrentData { private int data; private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); public int readData() { rwLock.readLock().lock(); // 使用读锁 try { // 读取数据 return data; } finally { rwLock.readLock().unlock(); } } public void writeData(int value) { rwLock.writeLock().lock(); // 使用写锁 try { this.data = value; } finally { rwLock.writeLock().unlock(); } } } public class Main { public static void main(String[] args) { ConcurrentData sharedData = new ConcurrentData(); // 启动多个线程进行并发读 for (int i = 0; i < 5; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " reads: " + sharedData.readData()); }).start(); } } }
在这个例子中,ConcurrentData 类使用了 ReentrantReadWriteLock 来允许多个线程并发读取数据,但当数据被写入时,其他线程(无论读或写)都会被阻塞,直到写操作完成。
总结
本文我们说了多线程、异步编程、并发写、并发读的相关知识。有什么说的不对的地方欢迎各位小伙伴与我私聊讨论。
下一篇文章我会继续说,解决并发读写用到的锁和 MVCC 等知识,让大家对并发编程有一个全局的认识。
可能有小伙伴觉得光了解这些概念没有用,其实是非常有用的。大家不用着急,再下下篇文章中我会从一个大数据框架的源码去给大家解剖,来说明熟练掌握这些并发知识是非常有必要的,我们拭目以待。
这篇关于多线程、异步编程、并发读写 新认识的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-23Fluss 写入数据湖实战
- 2024-12-22揭秘 Fluss:下一代流存储,带你走在实时分析的前沿(一)
- 2024-12-20DevOps与平台工程的区别和联系
- 2024-12-20从信息孤岛到数字孪生:一本面向企业的数字化转型实用指南
- 2024-12-20手把手教你轻松部署网站
- 2024-12-20服务器购买课程:新手入门全攻略
- 2024-12-20动态路由表学习:新手必读指南
- 2024-12-20服务器购买学习:新手指南与实操教程
- 2024-12-20动态路由表教程:新手入门指南
- 2024-12-20服务器购买教程:新手必读指南