一份针对于新手的多线程实践--进阶篇

简介: 在上文《一份针对于新手的多线程实践》留下了一个问题:这只是多线程其中的一个用法,相信看到这里的朋友应该多它的理解更进一步了。再给大家留个阅后练习,场景也是类似的:在 Redis 或者其他存储介质中存放有上千万的手机号码数据,每个号码都是唯一的,需要在最快的时间内把这些号码全部都遍历一遍。

遍历数据方案


有没有一种利用多线程加载效率高,并且线程之间互相不需要竞争锁的方案呢?


下面来看看这个方案:


首先在存储这千万号码的时候我们把它的号段单独提出来并冗余存储一次。


比如有个号码是 18523981123 那么就还需要存储一个号段:1852398


这样当我们有以下这些号码时:


18523981123 18523981124 18523981125 13123874321 13123874322 13123874323


我们就还会维护一个号段数据为:


1852398 1312387


这样我想大家应该明白下一步应当怎么做了吧。


在需要遍历时:


  • 通过主线程先把所有的号段加载到内存,即便是千万的号码号段也顶多几千条数据。


  • 遍历这个号段,将每个号段提交到一个 task 线程中。


  • 由这个线程通过号段再去查询真正的号码进行遍历。


  • 最后所有的号段都提交完毕再等待所有的线程执行完毕即可遍历所有的号码。


这样做的根本原因其实是避免了线程之间加锁,通过号段可以让每个线程只取自己那一部分数据。


可能会有人说,如果号码持续增多导致号段的数据也达到了上万甚至几十万这怎么办呢?


那其实也是同样的思路,可以再把号段进行拆分。


比如之前是 1852398 的号段,那我继续拆分为 1852


这样只需要在之前的基础上再启动一个线程去查询子号段即可,有点 fork/join 的味道。


这样的思路其实也和 JDK1.7 中的 ConcurrentHashMap 类似,定位一个真正的数据需要两次定位。



分布式方案


上面的方案也是由局限性的,毕竟说到底还是一个单机应用。没法扩展;处理的数据始终是有上限。


这个上限就和服务器的配置以及线程数这些相关,说的高大上一点其实就是垂直扩展增加单机的处理性能。


因此随着数据量的提升我们肯定得需要通过水平扩展的方式才能达到最好的性能,这就是分布式的方案。


假设我现在有上亿的数据需要遍历,但我当前的服务器配置只能支撑一个应用启动 N 个线程 5 分钟跑5000W 的数据。


于是我水平扩展,在三台服务器上启动了三个独立的进程。假设一个应用能跑 5000W ,那么理论上来说三个应用就可以跑1.5亿的数据了。


但这个的前提还是和上文一样:每个应用只能处理自己的数据,不能出现加锁的情况(这样会大大的降低性能)。


所以我们得对刚才的号段进行分组。


先通过一张图来直观的表示这个逻辑:



假设现在我有 9 个号段,那么我就得按照图中的方式把数据隔离开来。


第一个数据给应用0,第二个数据给应用1,第三个数据给应用2。后面的数据以此类推(就是一个简单的取模运算)。


这样就可以将号段均匀的分配给不同的应用来进行处理,然后每个应用再按照上文提到的将分配给自己的号段丢到线程池中由具体的线程去查询、遍历即可。


分布式带来的问题


这样看似没啥问题,但一旦引入了分布式之后就不可避免的会出现 CAP 的取舍,这里不做过多讨论,感兴趣的朋友可以自行搜索。


首先要解决的一个问题就是:


这三个应用怎么知道它自己应该取哪些号段的数据呢?比如 0 号应用就取 0 3 6(这个相当于号段的下标),难道在配置文件里配置嘛?


那如果数据量又增大了,对应的机器数也增加到了 5 台,那自然 0 号应用就不是取 0 3 6 了(取模之后数据会变)。


所以我们得需要一个统一的调度来分配各个应用他们应当取哪些号段,这也就是数据分片


假设我这里有一个统一的分配中心,他知道现在有多少个应用来处理数据。还是假设上文的三个应用吧。


在真正开始遍历数据的时候,这个分配中心就会去告诉这三个应用:


你们要开始工作了啊,0 号应用你的工作内容是 0 3 6,1 号应用你的工作内容是 1 4 7,2 号应用你的工作内容是 2 5 8


这样各个应用就知道他们所应当处理的数据了。


当我们新增了一个应用来处理数据时也很简单,同样这个分配中心知道现在有多少台应用会工作。


他会再拿着现有的号段对 4(3+1台应用) 进行取模然后对数据进行重新分配,这样就可以再次保证数据分配均匀了。


只是分配中心如何知道有多少应用呢,其实也简单,只要中心和应用之间通信就可以了。比如启动的时候调用分配中心的接口即可。


上面提到的这个分配中心其实就是一个常见的定时任务的分布式调度中心,由它来统一发起调度,当然分片只是它其中的一个功能而已(关于调度中心之后有兴趣再细说)。


总结


本次探讨了多线程的更多应用方式,如要是如何高效的运行。最主要的一点其实就是尽量的避免加锁。


同时对分布式水平扩展谈了一些处理建议


相关文章
|
21天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
1月前
|
安全 Java 调度
Java中的多线程编程:从理论到实践
【2月更文挑战第30天】本文旨在深入探讨Java中的多线程编程。我们将从基础的理论出发,理解多线程的概念和重要性,然后通过实际的Java代码示例,展示如何创建和管理线程,以及如何处理线程间的同步和通信问题。最后,我们还将讨论Java并发库中的一些高级特性,如Executor框架和Future接口。无论你是Java初学者,还是有经验的开发者,本文都将为你提供有价值的见解和实用的技巧。
|
3月前
|
存储 安全 调度
探索Python的多线程编程:原理与实践
探索Python的多线程编程:原理与实践
60 0
|
22天前
|
Java 程序员 调度
Java中的多线程编程:基础知识与实践
【4月更文挑战第5天】 在现代软件开发中,多线程编程是一个不可或缺的技术要素。它允许程序员编写能够并行处理多个任务的程序,从而充分利用多核处理器的计算能力,提高应用程序的性能。Java作为一种广泛使用的编程语言,提供了丰富的多线程编程支持。本文将介绍Java多线程编程的基础知识,并通过实例演示如何创建和管理线程,以及如何解决多线程环境中的常见问题。
|
4月前
|
数据采集 人工智能 数据可视化
Scala多线程爬虫程序的数据可视化与分析实践
Scala多线程爬虫程序的数据可视化与分析实践
|
2天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
9天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
16天前
|
Java 调度
Java中的多线程编程:从理论到实践
【4月更文挑战第11天】在现代软件开发中,多线程编程是一个重要的概念。它允许多个线程同时运行,从而提高了程序的执行效率和响应速度。本文将深入探讨Java中的多线程编程,包括基本概念、实现方法以及实际应用案例。我们将从理论出发,然后通过实例来加深理解,最后讨论多线程编程的优势和挑战。
|
20天前
|
Java API UED
Java中的多线程并发编程实践
【4月更文挑战第7天】在现代软件开发中,多线程技术是提高程序性能和响应速度的重要手段。Java语言提供了丰富的多线程支持,包括线程的创建、管理和同步机制。本文将深入探讨Java多线程的基本概念、实现方式以及常见的并发问题,并通过实例代码演示如何高效地利用多线程技术。
11 1
|
25天前
|
Java 开发者
Java中的多线程编程技巧与实践
多线程编程是Java开发中的重要部分,它可以提高程序的性能和响应速度。本文将介绍一些Java中的多线程编程技巧与实践,包括线程创建、同步机制、线程池以及常见的多线程编程陷阱和解决方案。通过深入理解这些技巧,可以帮助开发者更好地利用Java多线程特性,提高程序的效率和稳定性。
8 0