无论 SO 和其他网站上的其他问题问“我如何编写并发代码? ”,答案总是涉及非常模糊的解释,例如“检查数据依赖关系”或代码内部的“相互依赖关系”。我想知道这些神秘的依赖项实际上是什么样的 Java 代码!?!
- 一段代码的具体示例可以很容易地并行化,因为它的一部分不依赖于另一部分?
- 由于存在这些依赖关系,一段代码必须是串行的具体示例是什么?
- 这些依赖关系的存在如何影响是否使用线程池的决定?
我想我只是没有在这里看到“树林中的森林”。提前致谢!
无论 SO 和其他网站上的其他问题问“我如何编写并发代码? ”,答案总是涉及非常模糊的解释,例如“检查数据依赖关系”或代码内部的“相互依赖关系”。我想知道这些神秘的依赖项实际上是什么样的 Java 代码!?!
我想我只是没有在这里看到“树林中的森林”。提前致谢!
下面是一些简单的例子:
1.无依赖,易于并行化:
int[] array = new int[size];
for(int i = 0; i < size; i++) {
array[i] = array[i] * array[i];
}
每个项目都是独立计算的。
2.对上一次迭代的依赖:
int[] array = new int[size];
for(int i = 1; i < size; i++) {
array[i] = array[i - 1] * 2 + array[i];
}
array[3]
除非你有,否则你无法计算array[2]
(也许在某些情况下,你可以推导出一个允许重组的公式,但你明白了)。
线程池的使用与依赖关系的存在或不存在真正相关。这些概念是正交的。线程池只是回收线程的一种有效方式,线程通常是系统中昂贵的资源。
假设您想编写一个银行账户对象并且您想维护一个不变量——货币永远不会由于对银行账户的操作而神奇地创建或销毁。
你可能会写
int balance;
void deposit(int amount) {
if (amount < 0) { throw ... }
long after = balance + amount;
if (after > Integer.MAX_VALUE) { throw ... }
// DANGER 1
balance = (int) after;
}
void withdraw(int amount) {
if (amount < 0 || amount > balance) { throw ... }
// DANGER 2
balance -= amount;
}
考虑当一个线程在任一 DANGER 评论处暂停并同时发生另一次存款或取款时会发生什么。
如果一笔存款开始并在危险 1 处暂停,然后发生另一笔存款,然后第一笔存款结束,则存款可能会丢失,从而破坏价值。
如果一次提款开始,在 DANGER 2 处暂停,然后发生存款,然后提款结束,则提款可能会使余额变为负数。
如果存款开始,在 DANGER 1 处暂停,然后发生提款,然后存款结束,则可以创建货币。
解决这个问题的标准方法是说,计算after
值和分配的代码balance = (int) after
是一个关键部分,并且必须在没有对其任何依赖项进行任何干预更改的情况下发生:balance
.
类似地,安全检查withdrawal
和-=
操作形成一个关键部分——-=
如果检查仍然不正确,则操作不应发生。
为了获得额外的乐趣,请考虑可能发生的事情,因为a -= b
实际上有几个声明:
int x = b;
int y = a;
a = y - x;
这些都必须一起发生才能有意义,因此a -= b
具有标准含义取决于它的依赖关系a
,而b
不是在操作中间发生变化。
一段代码的具体示例可以很容易地并行化,因为它的一部分不依赖于另一部分?
并发应用程序建立在tasks
. 您可以将 a 标识task
为任何离散的工作单元。如果您可以识别出作为离散工作单元并具有明确任务边界Runnable
的代码部分,您可以为每个任务创建s 并以单独的方式运行它以Thread
实现并行性。实际上 aRunnable
代表Task
抽象。
一个示例是需要加载多个文件进行处理的进程。文件的加载是一个独立的工作单元,可以由线程在后台完成,不会阻塞代码流
由于存在这些依赖关系,一段代码必须是串行的具体示例是什么?
如果Task 2
依赖于Task 1
then的计算/处理Task 2
必须等待完成Task 1
,从而序列化序列。
一个例子是在后台加载一个文件,然后在文件中搜索一个键
这些依赖关系的存在如何影响是否使用线程池的决定?
他们没有。创建 aThread
是一项昂贵的操作,并且Thread Pools
是重用线程的构造设计,以避免创建新线程的开销。在 中,您Thread Pool
只需传递s 并负责将 分配给线程或在需要时创建新线程。您还可以通过线程池等定义策略。但它们是并发程序的工具。您应该已经确定了要提交到线程池执行的任务。Tasks
Runnable
Thread Pool
Task
一个没有依赖关系的简单示例是遍历集合的任何循环,对每个项目执行相同的工作(映射操作,在 FP 中)。确实存在依赖关系的一个典型示例是具有大量 db 提取的业务逻辑,基于它们的决策,然后进一步提取,所有这些都在嵌套的 ifs 中——大量数据处理,但每一步都取决于上一步的结果。