网站建设及推广图片/网站权重什么意思
多线程(三)
续上文,多线程(二),我们已经讲了
- 创建线程
Thread
的一些重要的属性和方法
那么接下来,我们继续来体会了解多线程吧~
文章目录
- 多线程(三)
- 线程启动 start
- start与run的区别
- 中断线程 interrupt
- 方法一
- 方法二
- 线程等待 join
- 线程状态
- 线程安全
- 线程安全问题的原因
- synchronized
线程启动 start
其实在之前的两篇文章中我,我们就见识过线程启动,也就是t.start();
,其实也就是start
方法。
start
方法内部,实际上是调用了系统api
,在系统内核创建线程。
而我们随之见识的t.run();
也就是run
方法,它只是单纯的描述了该线程要执行什么内容,是会在start
创建好线程之后自动被调用的~
start与run的区别
虽然我们看起来的效果是相似的,实际上本质上的区别就是在于是否是系统内部创建出的新的线程
中断线程 interrupt
所谓中断线程,也就是任一个线程停止运行(销毁),而在Java
中,要销毁/终止进程,做法是比较唯一的,就是让run
方法早点结束。(不过在C++
中,他是有能力直接强行终止一个正在运行的进程的,好暴力的呢🥵🥵🥵🥵,不过就会导致线程活干一半,环境会残留一些数据~)
所以我们还是就Java
来看(Java
生态多好~)
方法一
可以在代码中手动创建出标志位,来作为run
方法执行结束的循环
很多线程,执行时间久,往往就是因为写了一个循环,循环要持续执行,所以要想让run
执行结束,就是让循环尽快退出~
见以下代码:
public class Demo8 {private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() ->{while (!isQuit){//此处的打印可以替换成任意的逻辑来表示线程的实际工作内容System.out.println("线程工作中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程工作完毕");});t.start();Thread.sleep(5000);isQuit = true;System.out.println("已过5s 设置 isQuit 为 true");}
}
这里我们是设了一个成员变量isQuit
来作为标志位~
这里我们抛出一个疑问,要是我们将isQuit
改成main
方法内的局部变量,此时的程序是否还能完成中断操作?
public class Demo8_change {//private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {boolean isQuit = false;Thread t = new Thread(() ->{while (!isQuit){//此处的打印可以替换成任意的逻辑来表示线程的实际工作内容System.out.println("线程工作中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程工作完毕");});t.start();Thread.sleep(5000);isQuit = true;//常量变了System.out.println("已过 5s 设置 isQuit 为 true");}
}
哈哈,程序报错了
为什么呢?
这其实就是线程和主线程在读取和写入isQuit
变量时没有使用同步机制,这也涉及到了lambda
表达式中的一个语法规则,变量捕获。
lambda
表达式里面的代码,是可以自动捕获到上层作用域涉及到的局部变量的,也就是我们上面代码中的isQuit
。所谓的变量捕获,就是让lambda
表达式把当前作用域中的变量在lambda
内部复制了一份(此时外部是否销毁,无所谓)
而且在Java中,变量捕获语法,还有一个前提:就是必须只能捕获一个final
或者实际上是final
的值。
boolean isQuit = false;
虽然没有使用final
,但是却没有实际修改内容,他就是实际上的final
~
下面举个例子再了解了解:
public class VariableCaptureExample {public static void main(String[] args) {int num = 10; // 外部作用域的局部变量// 使用Lambda表达式引用外部作用域的变量Runnable r1 = () -> {System.out.println(num); // 引用外部作用域的变量};r1.run(); // 输出结果为:10// 使用内部类引用外部作用域的变量Runnable r2 = new Runnable() {@Overridepublic void run() {System.out.println(num); // 引用外部作用域的变量}};r2.run(); // 输出结果为:10}
}
在上述示例中,Lambda表达式和内部类都引用了外部作用域中的变量num
。当Lambda表达式或内部类被创建时,变量num
会被捕获并保存在生成的对象中,以供后续使用。
需要注意的是,被捕获的局部变量应当是有效的(final或事实上的final)。如果在Lambda表达式或内部类中尝试修改被捕获的变量,将会导致编译错误。例如,在上述示例中,如果尝试修改num
的值,编译器就会报错。这是因为被捕获的局部变量应当保持不可变,以确保代码的一致性。
当然,此处Java
的设定,并不够科学。这里的final
的限制,很多时候是个比较麻烦的事情,相比之下,JS
这里的设定更合适一些.lambda
(不只是lambda
)都是可以捕获外部的变量(JS
天然就有一个作用域链),可以保证捕获的变量是同一个变量,并且会自动的调整变量的生命周期.
方法二
调用 interrupt()
方法来通知
不过要中断进程,方法一显然不太优雅~因为它:
- 需要手动创建变量
- 当线程内部在
sleep
的时候,主线程修改变量,新线程内部不能及时响应
见以下例子
// 线程终止 - 优雅的方式
public class Demo9 {public static void main(String[] args) {Thread t = new Thread(()->{//Thread 类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束while (!Thread.currentThread().isInterrupted()){System.out.println("线程工作中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace(); }}});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("让 t 线程终止");t.interrupt();}
}
而 t.interrupt();
这个操作,就是将上述代码的Thread
对象内部的标志位设置为true
同时即使线程内部出现阻塞sleep
,也是可以用这个方法唤醒的~
正常来说,sleep
会休眠到时间到,才能唤醒,此处给出的interrupt
就可以使sleep
内部触发一个异常,从而提前被唤醒
而方法一中我们自己手动定义标志位,无法实现这个效果的~
我们看看上述代码的运行效果
oh,my god~异常确实是报出来了,但是上述的线程t
依然在运行,它并没有真的结束。
实际上,这是因为interrupt
唤醒线程之后,同时会清除刚才设置的标志位,这样就会导致我们刚才“设置标志位”这样的效果好像没生效一样~
正式一点来说就是:因为线程在执行Thread.sleep()
方法时,会抛出InterruptedException
异常。当t
线程抛出该异常时,catch
块中的代码会被执行,并且线程的中断状态会被重置为false
。因此,尽管主线程调用了t.interrupt()
方法,但此时线程的中断状态为false
,所以t
线程可以继续正常运行。
实际上这样的设定也是有它的道理所在:
这样的设计是为了给线程处理中断的机会,通过捕获InterruptedException
异常,线程可以在收到中断信号时进行必要的清理工作,并使用自定义逻辑来决定是否立即停止线程。
也就是让我们有更多的操作空间,而可操作空间的前提就是通过“异常”方式唤醒的。
因此如果希望t
线程在收到中断信号后立即终止,我们可以在catch
块中使用break;
语句来跳出循环,以实现快速结束线程。
线程等待 join
让一个线程等待,等待另一个线程执行结束,再继续执行,本质上就是控制线程结束的顺序~
join
就是实现线程等待的效果,在主线程中调用t.join();
此时就是主线程等待t
线程先结束。
join
的工作过程:
- 如果
t
线程正在运行中,此时调用join
的线程就会阻塞,一直阻塞到t
线程执行结束为止 - 如果
t
线程已经执行结束了,此时调用join
线程,就直接返回,不会涉及到阻塞~
public class Demo10 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println(" t 线程工作中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();//让主线程来等待 t 线程执行结束//一旦调用 join,主线程就会触发阻塞, 此时 t 线程就会趁机完成后续的工作//一旦阻塞到 t 执行完毕了,join才会解除阻塞,继续执行System.out.println("join 开始等待");t.join(1000);//最好是有时间等待,不要死等System.out.println("join 等待结束");}
}
/*
join 开始等待t 线程工作中t 线程工作中
join 等待结束t 线程工作中t 线程工作中t 线程工作中
/*
这里举个例子:
有一天我约女神出来,19:00
在学校门口碰头~~
-
如果我先到了,发现女神还没来,就要阻塞等待,等到女神来了之后,我俩就可以一起去🥵🥵🥵🥵了.
-
女神先到了.当我来到校门口的时候,虽然时间还不到
19:00
,但是我看到女神已经在了.此时我俩直接出发去🥵🥵🥵🥵就可以了.就不需要等待 -
我来了之后,等了很久,女神还没出现,我仍然继续等.…
join
默认是"死等",“不死不休”)
一般来说,等待操作都是带有一个"超时时间”
sleep有一定的调度开销
//证明sleep(1000)实际上并不精确public class Demo11 {public static void main(String[] args) throws InterruptedException {/*System.out.println("开始: " + System.currentTimeMillis());Thread.sleep(1000);System.out.println("结束: " + System.currentTimeMillis());*/long beg = System.currentTimeMillis();Thread.sleep(1000);long end = System.currentTimeMillis();System.out.println("时间:" + (end - beg) + "ms");}
}
//时间:1009ms
系统会按照1000ms
这个时间来控制让线程休眠
但是当1000ms
时间到了之后,系统会唤醒这个线程(阻塞 -> 就绪)
但是不是说这个线程就成了就绪状态,就能够立即回到cpu
上运行,(因为这中间会有一个“调度”的开销)
对于windows
和Linux
这些系统来说,调度开销是很大的,可能会达到ms
级别
线程状态
进程的状态,最核心的,一个是就绪状态,阻塞状态.(对于线程同样适用)
以线程为单位进行调度的.
在Java中,又给线程赋予了一些其他的状态
NEW
:安排了工作,还未开始行动,Thread
对象有了,start
方法还没调用TERMINATED
:工作完成了,Thread
对象还在,内核中的进程已经没了RUNNABLE
:可工作的.又可以分成正在工作中和即将开始工作,就绪状态(线程已经在cpu
上执行了/线程正在排队等待上cpu
执行)TIMED_WAITING
:这几个都表示排队等着其他事情,阻塞,由于sleep
这种固定时间的方式产生的阻塞WAITING
:这几个都表示排队等着其他事情,由于wait
这种不固定时间的方式产生的阻塞BLOCKED
:这几个都表示排队等着其他事情,由于所竞争导致的阻塞
public class Demo12 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 在调用 start 之前获取状态, 此时就是 NEW 状态System.out.println(t.getState());t.start();for (int i = 0; i < 5; i++) {System.out.println(t.getState());Thread.sleep(1000);}t.join();// 在线程执行之后,获取线程的状态,此时是 TERMINATED 状态System.out.println(t.getState());}
}
线程安全
所谓的线程安全,就是说有些代码在单个线程环境中执行,是完全正确的,但是如果是相同的代码,让其在多个线程的环境中去同时执行,此时就会出现bug
,这也就是线程安全问题~
下面给出示例代码:
public class Demo14 {//此处定义一个 int 类型的变量private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{// 对 count 变量进行自增 5w 次for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{// 对 count 变量进行自增 5w 次for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();// 如果没有这两 join ,肯定是不行的,线程还没自增完,就开始打印了,很可能打印出来的 count 就是个 0t1.join();t2.join();//预期结果应该是 10wSystem.out.println("count: " + count);}
}
//count: 52947
以上的代码就是非常典型的线程安全问题~
所以在解决这个bug'
前,我们要知道count++
的本质~
count++
的本质上是分三步操作的,站在cpu
的角度上,count++
是由cpu
通过三个指令来实现的:
load
:把数据从内存读取到cpu
寄存器中add
:把寄存器中的数据进行+1
save
:把寄存器中的数据,保存到内存中
如果是多个线程执行上述代码,由于线程之间的调度顺序,是"随机”的,就会导致在有些调度顺序下,上述的逻辑就会出现问题.
结合上述讨论,就意识到了,在多线程程序中,最困难的一点:
线程的随机调度,使两个线程执行逻辑的先后顺序,存在诸多可能.
我们必须要保证在所有可能的情况下,代码都是正确的!!
在解决这个问题之前,我们得知道产生线程安全问题的原因是什么:
线程安全问题的原因
-
操作系统中,线程的调度顺序是随机的(抢占性执行),这也就是万恶之源
-
两个线程,针对同一个变量进行修改(有些情况我们是可以通过修改代码结构来规避上述问题,但是也有很多情况是调整不了的)
-
修改操作,不是原子的
此处给的
count++
就属于是非原子操作(先读,再修改)类似的,如果在一段逻辑中,需要根据一定的条件来决定是否修改,也是存在类似问题
(假设
count++
是原子的,也就是说有一个cpu
指令可以一次完成上述的count++
三步操作) -
内存可见性问题
-
指令重排序问题
那么知道了原因,我们就有相对应的解决方法了~
我们的思路就是:
- 通过修改代码结构来规避上述问题,虽然也有很多情况是调整不了的
- 想办法让
count++
的三步走变成原子性的
那么这里我们就选择:加锁!!!!!!!!!
最常用的方法就是synchronized
关键字
synchronized
synchronized
是Java中的关键字,用于实现线程的同步。它可以应用于方法或代码块上。
所以synchronized
在使用的时候,我们需要搭配一个代码块{}
这样子进入{
就会加锁,出}
就会解锁
在已经加锁的状态中,另一个线程尝试同样加这个锁,就会产生“锁冲突/锁竞争”,后一个线程就会阻塞等待,一直等到前一个线程解锁为止~
所以这里我们给出修改后的代码:
public class Demo13 {
//此处定义一个 int 类型的变量private static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(()->{// 对 count 变量进行自增 5w 次for (int i = 0; i < 50000; i++) {synchronized (locker){count++;}}});Thread t2 = new Thread(()->{// 对 count 变量进行自增 5w 次for (int i = 0; i < 50000; i++) {synchronized (locker){count++;}}});// t1.start();
// t2.start();// 如果没有这两 join ,肯定是不行的,线程还没自增完,就开始打印了,很可能打印出来的 count 就是个 0
// t1.join();
// t2.join();//改进:>t1.start();t1.join();t2.start();t2.join();System.out.println("count: " + count);//100000
//在原来的代码中,t1.start()和t2.start()的顺序是固定的,不论t1和t2线程的逻辑如何,主线程会立即启动两个新线程,然后等待它们执行完毕。这种方式适用于两个线程之间没有依赖关系的情况。
//因此,改变t1.start()和t2.start()的调用顺序以及使用join()方法,可以控制线程的执行顺序和依赖关系,满足具体的业务需求。}
}
//count: 100000
如果两个线程是在针对同一个对象加锁,就会有锁竞争
如果不是针对同一个对象加锁,就不会有锁竞争,仍然是并发执行!
我们举个例子:
把锁当作一个小妹妹,你去表白,成功了,妹子到手了,也相当于你给妹子加锁了,这时候别的男的他想要你的小妞,他就得阻塞等待,等你们分手了,他才能接盘(排除妹子绿你的情况哈~)
但是那个男的去追别的女生,就不会受你的影响(除非你这人遍地撒花~)
如图:
至此,多线程(三)暂时讲到这里,这里暂时synchronized
开了个头,接下来会继续更新,敬请期待~
相关文章:

【JavaEE】多线程(三)
多线程(三) 续上文,多线程(二),我们已经讲了 创建线程Thread的一些重要的属性和方法 那么接下来,我们继续来体会了解多线程吧~ 文章目录 多线程(三)线程启动 startsta…...

9.25day5---Qt
登录页面设计,建立用户注册以及登录的数据库,数据库保存用户名和密码 (ps:本篇只完成了登录功能,其他功能,请见下篇嘿嘿。) 再次注册则失败: 代码如下: 头文件: 登录…...

wpf制作自定义控件,并触发外部路由事件
目的是在前端增加一个自定义控件里的button后,按下动作可以调用使用该控件的页面的事件 首先在前端增加自定义控件里加入一个button,在其cs页面里注册点击事件 var btnAdd GetTemplateChild("btnAdd") as FlatButton;if (btnAdd ! null){btn…...

axios全局路由拦截的设置方法
一个项目中如果http请求发生了错误/异常,比如返回码4xx(表示没有授权,登录过期等),我们希望能够在axios在第一时间就能拦截获取到,然后直接提示报错的错误信息,而不是在发起请求的地方ÿ…...

XSS跨站脚本攻击
XSS全称(Cross Site Scripting)跨站脚本攻击,XSS属于客户端攻击,受害者最终是用户,在网页中嵌入客户端恶意脚本代码,最常用javascript语言。(注意:叠成样式表CSS已经被占用所以叫XSS)…...

Java8实战-总结33
Java8实战-总结33 重构、测试和调试使用 Lambda 重构面向对象的设计模式策略模式模板方法 重构、测试和调试 使用 Lambda 重构面向对象的设计模式 新的语言特性常常让现存的编程模式或设计黯然失色。比如, Java 5中引入了for-each循环,由于它的稳健性和…...

Postman 的使用教程(详细)
Postman 使用教程 1. 是什么 Postman 是一个接口测试工具软件,可以帮助开发人员管理测试接口。 官网:https://www.getpostman.com/ 2. 安装 建议通过官网下载安装,不要去那些乱七八糟的下载平台,或者留言获取 官网下载地址&am…...

单元测试 —— JUnit 5 参数化测试
JUnit 5参数化测试 目录 设置我们的第一个参数化测试参数来源 ValueSourceNullSource & EmptySourceMethodSourceCsvSourceCsvFileSourceEnumSourceArgumentsSource参数转换参数聚合奖励总结 如果您正在阅读这篇文章,说明您已经熟悉了JUnit。让我为您概括一下…...

uview组件库的安装
更多的请查看官方文档uView 2.0 - 全面兼容 nvue 的 uni-app 生态框架 - uni-app UI 框架 (uviewui.com) // 如果您的根目录没有package.json文件的话,请先执行如下命令: // npm init -y 安装 npm install uview-ui2.0.36 // 更新 // npm update uvie…...

skywalking入门
参考: https://www.jianshu.com/p/ffa7ddcda4ab 参考: https://developer.aliyun.com/article/1201085 skywalking(APM) 调用链路分析以及应用监控分析工具 Skywalking主要由三大部分组成:agent、collector、webapp-…...

【Java 基础篇】Java多线程实现文件上传详解
文件上传是Web应用程序中常见的功能之一,用户可以通过网页将文件从本地计算机上传到服务器。在处理大文件或多用户并发上传的情况下,为了提高性能和用户体验,常常使用多线程来实现文件上传功能。本文将详细介绍如何使用Java多线程实现文件上传…...

【计算机基础】VS断点调试,边学边思考
📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…...

BD就业复习第五天
1. 核心组件的优化:hive、spark、flink 针对Hive、Spark和Flink这三个核心组件,以下是它们的优化和一些常见面试题以及详细的回答: 1. Hive 优化 面试问题1:什么是Hive?为什么需要对Hive进行优化? 回答…...

ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the ‘ssl‘
报错: ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1, currently the ‘ssl’ module is compiled with OpenSSL 1.1.0h 27 Mar 2018.解决办法:将urllib3的版本降低 pip install urllib31.26.15参考 python包报错ImportError: urllib3 v2.…...

Qt5开发及实例V2.0-第十二章-Qt多线程
Qt5开发及实例V2.0-第十二章-Qt多线程 第12章 Qt 5多线程12.1 多线程及简单实例12.2 多线程控制12.2.1 互斥量12.2.2 信号量12.2.3 线程等待与唤醒 12.3 多线程应用12.3.1 【实例】:服务器编程12.3.2 【实例】:客户端编程 本章相关例程源码下载1.Qt5开发…...

Windows 修改系统默认字体
Windows Registry Editor Version 5.00; 重装机后电脑屏幕及字体调整.reg.lnk ;; 显示器分辨率: 3840*2160 ;; 自定义缩放: 266 ;; 辅助功能 - 文本大小 - 110% ;; 最后 ClearType 文本调谐器; https://www.cnblogs.com/bolang100/p/8548040.html#WINDOWS 10 显示中的仅更改文…...

图像处理软件Photoshop 2024 mac新增功能
Photoshop 2024 mac是一款图像处理软件的最新版本。ps2024提供了丰富的功能和工具,使用户能够对照片、插图、图形等进行精确的编辑和设计。 Photoshop 2024 mac软件特点 快速性能:Photoshop 2024 提供了更快的渲染速度和更高效的处理能力,让用…...

JavaScript之观察者模式
本文作者为 360 奇舞团前端开发工程师 概述 在日常开发中,开发人员经常使用设计模式来解决软件设计中的问题。其中,观察者模式是一种常用的模式,它可以帮助开发人员更好地处理对象之间的通信。在 JavaScript 中,观察者模式的应用非…...

深入了解ln命令:创建硬链接和符号链接的实用指南
文章目录 1. 引言1.1 关于ln命令1.2 ln命令的作用和用途 2. 基本用法2.1 创建硬链接2.2 创建符号链接2.3 区别硬链接和符号链接 3. 操作示例3.1 创建硬链接的示例3.2 创建符号链接的示例3.3 查看链接信息 4. 注意事项和常见问题4.1 文件路径4.2 软链接的相对路径4.3 软链接的更…...

虚拟IP技术
1.说明 虚拟IP(Virtual IP Address,简称VIP)是一个未分配给真实弹性云服务器网卡的IP地址。 弹性云服务器除了拥有私有IP地址外,还可以拥有虚拟IP地址,用户可以通过其中任意一个IP(私有IP/虚拟IP…...

蓝桥杯 题库 简单 每日十题 day5
01 字符计数 字符计数 题目描述 给定一个单词,请计算这个单词中有多少个元音字母,多少个辅音字母。 元音字母包括a,e,i,o,u,共五个,其他均为辅音字母。 输入描述 输入格式: 输入一行࿰…...

【计算机网络】图解路由器(一)
图解路由器(一) 1、什么是路由器?2、什么是路由选择?3、什么是转发?4、路由器设备有哪些类型?5、根据性能分类,路由器有哪些类型?5.1 高端路由器5.2 中端路由器5.3 低端路由器 6、什…...

C语言文件的相关操作
C语言中文件的相关操作 文件的打开 使用文件的打开函数需要引入这个头文件:#include <fcntl.h> open函数 int open(char const *pathname, int flags, mode_t mode) 功能:打开已有的文件或者创建新文件参数 pathname:文件路径名&…...

Java入门级简单定时任务TimerTask
如果要执行一些简单的定时器任务,无须做复杂的控制,也无须保存状态,那么可以考虑使用JDK 入门级的定期器Timer来执行重复任务。 一、原理 JDK中,定时器任务的执行需要两个基本的类: java.util.Timer; java…...

Linux命令行教程:使用head和tail命令快速查看文件的开头和结尾
文章目录 简介A. 什么是head和tail命令B. head和tail命令的作用和用途 head命令A. 命令格式和语法B. 常见选项和参数1. -n:指定显示的行数2. -c:指定显示的字节数3. -v:显示文件名 C. 示例和应用实例1. 显示文件的前几行2. 显示多个文件的前几…...

[CISCN 2019 初赛]Love Math 通过进制转换执行命令
目录 hex2bin bin2hex base_convert 动态函数 第一种解法 通过get获取参数 绕过 第二种解法 读取请求头 getallheaders echo a,b 第三种解法 异或获得更多字符 这道题也是很有意思! 通过规定白名单和黑名单 指定了 函数为数学函数 并且参数也只能是规…...

【Linux】系统编程生产者消费者模型(C++)
目录 【1】生产消费模型 【1.1】为何要使用生产者消费者模型 【1.2】生产者消费者模型优点 【2】基于阻塞队列的生产消费者模型 【2.1】生产消费模型打印模型 【2.2】生产消费模型计算公式模型 【2.3】生产消费模型计算公式加保存任务模型 【2.3】生产消费模型多生产多…...

【数据结构】图的应用:最小生成树;最短路径;有向无环图描述表达式;拓扑排序;逆拓扑排序;关键路径
目录 1、最小生成树 1.1 概念 1.2 普利姆算法(Prim) 1.3 克鲁斯卡尔算法(Kruskal) 2、最短路径 2.1 迪杰斯特拉算法(Dijkstra) 2.2 弗洛伊德算法(Floyd) 2.3 BFS算法&…...

大数据驱动业务增长:数据分析和洞察力的新纪元
文章目录 大数据的崛起大数据的特点大数据技术 大数据驱动业务增长1. 洞察力和决策支持2. 个性化营销3. 风险管理4. 产品创新 大数据分析的新纪元1. 云计算和大数据示例代码:使用AWS的Elastic MapReduce(EMR)进行大数据分析。 2. 人工智能和机…...

科技云报道:分布式存储红海中,看天翼云HBlock如何突围?
科技云报道原创。 过去十年,随着技术的颠覆性创新和新应用场景的大量涌现,企业IT架构出现了稳态和敏态的混合化趋势。 在持续产生海量数据的同时,这些新应用、新场景在基础设施层也普遍基于敏态的分布式架构构建,从而对存储技术…...