- 浏览: 107092 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (99)
- 经济 (1)
- dwr (2)
- 测试 (0)
- java (29)
- resin (1)
- oracle (3)
- 感悟 (1)
- jvm (15)
- mina2 (5)
- j2se (12)
- linux (28)
- protobuf (1)
- tcp/ip (0)
- jdbc (0)
- 数据库 (4)
- 游戏 (0)
- 技术文档 (1)
- nosql (2)
- 算法 (2)
- apache (2)
- mysql (1)
- hashcode (1)
- spring (2)
- quartz (5)
- netcat (2)
- 分页 (1)
- 正则 (0)
- shell (1)
- lsof (1)
- nginx (1)
- git (1)
最新评论
-
fys124974704:
你试下将第三条写成以下这样,你会发现你的结论不对:select ...
ORACLE分页SQL语句 -
ikon:
两个乘数没有转成integer,而是当成字符串;BigInte ...
计算任意2个正整数的乘积 -
kidding87:
效率不是很高,思路没有问题,但是你的两个乘数输入都都转为Int ...
计算任意2个正整数的乘积 -
k1280000:
------------------------同意!
学习之道
语言、库和框架形成了我们编写程序的方式。Alonzo Church 早在 1934 年就曾表明,所有已知的计算性框架对于它们所能表示的程序集都是等价的,程序员实际编写的程序集是由特定语言形成的,而编程模型(由语言、库和框架驱动)可以简化这些语言的表达。 另一方面,一个时代的主流硬件平台形成了我们创建语言、库和框架的方法。Java 语言从一开始就能够支持线程和并发性;该语言包括像 随着多处理器系统价格降低,更多的应用程序需要使用这些系统提供的硬件并行性。而且程序员们发现,使用 Java 语言提供的低级原语和类库编写并发程序非常困难且容易出错。在 Java 5 中, 技术继续发展,硬件的趋势非常清晰;Moore 定律表明不会出现更高的时钟频率,但是每个芯片上会集成更多的内核。很容易想象让十几个处理器繁忙地处理一个粗粒度的任务范围,比如一个用户请求,但是这项技术不会扩大到数千个处理器 — 在很短一段时间内流量可能会呈指数级增长,但最终硬件趋势将会占上风。当跨入多内核时代时,我们需要找到更细粒度的并行性,否则将面临处理器处于空闲的风险,即使还有许多工作需要处理。如果希望跟上技术发展的脚步,软件平台也必须配合主流硬件平台的转变。最终,Java 7 将会包含一种框架,用于表示某种更细粒度并行算法的类:fork-join 框架。 如今,大多数服务器应用程序将用户请求-响应处理作为一个工作单元。服务器应用程序通常会运行比可用的处理器数量多很多的并发线程或请求。这是因为在大多数服务器应用程序中,对请求的处理包含大量 I/O,这些 I/O 不会占用太多的处理器(所有网络服务器应用程序都会处理许多的套接字 I/O,因为请求是通过套接字接收的;也会处理大量磁盘(或数据库)I/O)。如果每个任务的 90% 的时间用来等待 I/O 完成,您将需要 10 倍于处理器数量的并发任务,才能充分利用所有的处理器。随着处理器数量增加,可能没有足够的并发请求保持所有处理器处于繁忙状态。但是,仍有可能使用并行性来改进另一种性能度量:用户等待获取响应的时间。 一个典型网络服务器应用程序的例子是,考虑一个数据库服务器。当一个请求到达数据库服务器时,需要经过一连串的处理步骤。首先,解析和验证 SQL 语句。然后必须选择一个查询计划;对于复杂查询,数据库服务器将会评估许多不同的候选计划,以最小化预期的 I/O 操作数量。搜索查询计划是一种 CPU 密集型任务;在某种情况下,考虑过多的候选计划将会产生负面影响,但是如果候选计划太少,所需的 I/O 操作肯定比实际数量要多。从磁盘检索到数据之后,也许需要对结果数据集进行更多的处理;查询可能包含聚合操作,比如 SUM、AVERAGE,或者需要对数据集进行排序。然后必须对结果进行编码并返回到请求程序。 就像大多数服务器请求一样,处理 SQL 查询涉及到计算和 I/O。虽然添加额外的 CUP 不会减少完成 I/O 的时间(但是可以使用额外的内存,通过缓存以前的 I/O 操作结果来减少 I/O 数量),但是可以通过并行化来缩短请求处理的 CPU 密集型部分(比如计划评估和排序)的处理时间。在评估候选的查询计划时,可以并行评估不同的计划;在排序数据集时,可以将大数据集分解成更小的数据集,分别进行排序然后再合并。这样做会使用户觉得性能得到了提升,因为会更快收到结果(即使总体上可能需要更多工作来服务请求)。 合并排序是 divide-and-conquer 算法的一个例子,在这种算法中将一个问题递归分解成子问题,再将子问题的解决方案组合得到最终结果。 divide-and-conquer 算法也可用于顺序环境中,但是在并行环境中更加有效,因为可以并行处理子问题。 典型的并行 divide-and-conquer 算法的形式如清单 1 所示: 并行 divide-and-conquer 算法首先对问题进行评估,确定其大小是否更适合使用顺序解决方案;通常,可通过将问题大小与某个阙值进行比较完成。如果问题大到需要并行分解,算法会将其分解成两个或更多子问题,并以并行方式对子问题递归调用算法本身,然后等待子问题的结果,最后合并这些结果。用于选择顺序和并行执行方法的理想阙值是协调并行任务的成本。如果协调成本为 0,更多的更细粒度的任务会提供更好的并行性;在需要转向顺序方法之前,协调成本越低,就可以划分更细粒度的任务。 清单 1 中的示例使用了实际上并不存在的 INVOKE-IN-PARALLEL 操作;它的行为表现为当前任务是暂停的,并行执行两个子任务,而当前任务等待两个子任务的完成。然后就可以将两个子任务的结果进行合并。这种并行分解方法常常称作 fork-join,因为执行一个任务将首先分解(fork)为多个子任务,然后再合并(join)(完成后)。 清单 2 显示了一个适合使用 fork-join 解决方案的问题示例:在大型数组中搜索其中的最大元素。虽然这个示例非常简单,但是 fork-join 技术可用于各种各样的搜索、排序和数据分析问题。 注意 清单 3 演示了使用 fork-join 包的 fork-join 框架支持几种风格的 表 1 显示了在不同系统上从 500,000 个元素的数组中选择最大元素的结果,以及改变阙值,使顺序方法优于并行方法。对于大多数运行,fork-join 池中的线程数量与可用的硬件线程(内核数乘以每个内核中的线程数)相等。与顺序方法相比,这些线程数量呈现一种加速比(speedup)。 结果很令人振奋,因为它们在选择各种参数时显示了不错的加速比。因此,只要避免为问题和底层硬件选择完全不合理的参数,就会获得不错的结果。使用 chip-multithreading 技术,最优的加速比不太明显;像 Hyperthreading 这样的 CMT 方法所提供的性能要低于等价数量的实际内核提供的性能,但是性能损失取决于许多因素,包括正在执行的代码的缓存丢失率(miss rate)。 此处选择的顺序阙值范围从 500K(数组的大小,表示没有并行性)到 50。在这个例子中,阙值为 50 实在太小,有些不切实际,而且结果显示,顺序阙值太低时,fork-join 任务管理开销将起决定作用。但是表 1 也显示只要避免这种不切实际的过高和过低的参数,就会获得不错的结果。选择 可以有很多方法实现 清单 3 中演示的 fork-join 框架。可以使用原始的线程; 使用传统的线程池来实现 fork-join 也具有挑战性,因为 fork-join 任务将线程生命周期的大部分时间花费在等待其他任务上。这种行为会造成线程饥饿死锁(thread starvation deadlock),除非小心选择参数以限制创建的任务数量,或者池本身非常大。传统的线程池是为相互独立的任务设计的,而且设计中也考虑了潜在的阻塞、粗粒度任务 — fork-join 解决方案不会产生这两种情况。对于传统线程池的细粒度任务,也存在所有工作线程共享的任务队列发生争用的情况。 fork-join 框架通过一种称作工作窃取(work stealing) 的技术减少了工作队列的争用情况。每个工作线程都有自己的工作队列,这是使用双端队列(或者叫做 deque)来实现的(Java 6 在类库中添加了几种 deque 实现,包括 可以使用标准队列实现工作窃取,但是与标准队列相比,deque 具有两方面的优势:减少争用和窃取。因为只有工作线程会访问自身的 deque 的头部,deque 头部永远不会发生争用;因为只有当一个线程空闲时才会访问 deque 的尾部,所以也很少存在线程的 deque 尾部的争用(在 fork-join 框架中结合 deque 实现会使这些访问模式进一步减少协调成本)。跟传统的基于线程池的方法相比,减少争用会大大降低同步成本。此外,这种方法暗含的后进先出(last-in-first-out,LIFO)任务排队机制意味着最大的任务排在队列的尾部,当另一个线程需要窃取任务时,它将得到一个能够分解成多个小任务的任务,从而避免了在未来窃取任务。因此,工作窃取实现了合理的负载平衡,无需进行协调并且将同步成本降到了最小。 fork-join 方法提供了一种表示可并行化算法的简单方式,而不用提前了解目标系统将提供多大程度的并行性。所有的排序、搜索和数字算法都可以进行并行分解(以后,像 synchronized
和 volatile
这样的同步原语,而类库包含像 Thread
这样的类。然而,1995 年流行的并发原语反映了当时的硬件现状:大多数商用系统根本没有提供并行性,甚至最昂贵的系统也只提供了有限的并行性。当时,线程主要用来表示异步,而不是并发,而这些机制已足够满足当时的需求了。java.util.concurrent
包被添加到 Java 平台,它提供了一组可用于构建并发应用程序的组件:并发集合、队列、信号量、锁存器(latch)、线程池等等。这些机制非常适合用于粗任务粒度的程序;应用程序只需对工作进行划分,使并发任务的数量不会持续少于可用的处理器数量。通过将对单个请求的处理用作 Web 服务器、邮件服务器或数据库服务器的工作单元,应用程序通常能满足这种需求,因此这些机制能够确保充分利用并行硬件。
清单 1. 通用 divide-and-conquer 并行算法的伪代码。
// PSEUDOCODE
Result solve(Problem problem) {
if (problem.size < SEQUENTIAL_THRESHOLD)
return solveSequentially(problem);
else {
Result left, right;
INVOKE-IN-PARALLEL {
left = solve(extractLeftHalf(problem));
right = solve(extractRightHalf(problem));
}
return combine(left, right);
}
}
清单 2. 从大型数组中选择最大元素
public class SelectMaxProblem {
private final int[] numbers;
private final int start;
private final int end;
public final int size;
// constructors elided
public int solveSequentially() {
int max = Integer.MIN_VALUE;
for (int i=start; i<end; i++) {
int n = numbers[i];
if (n > max)
max = n;
}
return max;
}
public SelectMaxProblem subproblem(int subStart, int subEnd) {
return new SelectMaxProblem(numbers, start + subStart,
start + subEnd);
}
}
subproblem()
方法没有复制这些元素;它只是将数组引用和偏移复制到一个现有的数据结构中。这在 fork-join 问题实现中很常见,因为递归分解问题的过程将会创建大量的新 Problem
对象。 在这个例子中,搜索任务没有修改被搜索的数据结构,因此不需要维护每个任务的底层数据集的私有副本,也不会因复制而增加额外的开销。SelectMaxProblem
解决方案,Java 7 中已计划包含该包。JSR 166 Expert Group 正在公开开发这个包,使用的代码名称为 jsr166y,您可以单独下载它并在 Java 6 或更高版本中使用(它最终将会包含在包java.util.concurrent.forkjoin
中)。invoke-in-parallel
操作是用 coInvoke()
方法来实现的,该操作同时调用多个动作并等待所有动作完成。ForkJoinExecutor
跟 Executor
类似,因为它也是用来运行任务的,但它是专门针对计算密集型任务而设计的。这种任务不会被阻塞,除非它在等待由相同 ForkJoinExecutor
处理的另一个任务。ForkJoinTasks
,包括那些需要显式完成的,以及需要循环执行的。这里使用的 RecursiveAction
类直接支持 non-result-bearing 任务的并行递归分解风格;RecursiveTask
类解决 result-bearing 任务的相同问题(其他 fork-join 任务类包括 CyclicAction
、AsyncAction
和 LinkedAsyncAction
;要获得关于如何使用它们的更多细节,请查阅 Javadoc)。
清单 3. 使用 fork-join 框架解决选择最大值问题
public class MaxWithFJ extends RecursiveAction {
private final int threshold;
private final SelectMaxProblem problem;
public int result;
public MaxWithFJ(SelectMaxProblem problem, int threshold) {
this.problem = problem;
this.threshold = threshold;
}
protected void compute() {
if (problem.size < threshold)
result = problem.solveSequentially();
else {
int midpoint = problem.size / 2;
MaxWithFJ left = new MaxWithFJ(problem.subproblem(0, midpoint), threshold);
MaxWithFJ right = new MaxWithFJ(problem.subproblem(midpoint +
1, problem.size), threshold);
coInvoke(left, right);
result = Math.max(left.result, right.result);
}
}
public static void main(String[] args) {
SelectMaxProblem problem = ...
int threshold = ...
int nThreads = ...
MaxWithFJ mfj = new MaxWithFJ(problem, threshold);
ForkJoinExecutor fjPool = new ForkJoinPool(nThreads);
fjPool.invoke(mfj);
int result = mfj.result;
}
}
表 1. 在不同系统上从 500k 个元素的数组中运行 select-max 的结果
阙值 = 500k
阙值 = 50k
阙值 = 5k
阙值 = 500
阙值 = -50
Pentium-4 HT(2 个线程)
1.0
1.07
1.02
.82
.2
Dual-Xeon HT(4 个线程)
.88
3.02
3.2
2.22
.43
8-way Opteron(8 个线程)
1.0
5.29
5.73
4.53
2.03
8-core Niagara(32 个线程)
.98
10.46
17.21
15.34
6.49
Runtime.availableProcessors()
作为工作线程的数量通常会获得与理想结果相近的结果,因为在 fork-join 池中执行的任务都是和 CPU 绑定的,但是,只要避免设置过大或过小的池,这个参数对结果就不会有太大影响。MaxWithFJ
类中不需要显式同步。它操作的数据对于问题的生命周期来说是不变的,并且 ForkJoinExecutor
中有足够的内部同步可以保证问题数据对于子任务的可视性,也可以保证子任务结果对于跟它们结合的任务的可视性。Thread.start()
和 Thread.join()
提供了所有必要的功能。然而,这种方法需要的线程数可能比 VM 所能支持的数量更多。对于大小为 N(假设为一个很小的顺序阙值)的问题,将需要O(N) 个线程来解决问题(问题树深度为 log2N,深度为 k 的二进制树有 2k 个节点)。在这些线程中,半数线程会用几乎整个生命周期来等待子任务的完成。创建线程会占用许多内存,这使得这种方法受到限制(尽管这种方法也能工作,但是代码非常复杂,并且需要仔细针对问题大小和硬件进行参数调优)。ArrayDeque
和LinkedBlockingDeque
)。当一个任务划分一个新线程时,它将自己推到 deque 的头部。当一个任务执行与另一个未完成任务的合并操作时,它会将另一个任务推到队列头部并执行,而不会休眠以等待另一任务完成(像 Thread.join()
的操作一样)。当线程的任务队列为空,它将尝试从另一个线程的 deque 的尾部 窃取另一个任务。Arrays.sort()
这样的标准库机制将会使用 fork-join 框架,允许应用程序免费享有并行分解的益处)。随着处理器数量的增长,我们将需要在程序内部使用更多的并行性,以有效利用这些处理器;对计算密集型操作(比如排序)进行并行分解,使程序能够更容易利用未来的硬件。
发表评论
-
关于hashcode 里面 使用31 系数的问题
2012-03-27 09:16 869首先我们来了解一下h ... -
理解Java对象序列化
2012-02-15 09:38 731关于Java序列化的文章早 ... -
认识Arrays(一)打印
2012-02-03 16:52 609Arrays提供了一组操作array的静态方法。 一、基本类 ... -
apache.commons.io 笔记1
2012-01-19 17:13 1102看看,常见的东西都有了,如查询盘的剩余空间,文件夹大小, ... -
commons-io 自动加载配置文件
2012-01-19 16:55 812org.apache.commons.io.monitor.F ... -
oracle java数据类型对应表
2011-12-05 13:37 0[img]http://dl.iteye.com/upload ... -
Iterator和Enumeration的主要区别
2011-11-25 14:38 901(1)java中的集合类都提供了返回Iterator的方法 ... -
Java调用外部程序技巧
2011-11-08 09:17 835前些天使用Java调用外部程序的时候,发现线程会堵塞在wa ... -
浅析 Java Thread.join()
2011-10-29 09:25 908一、在研究join的用法之前,先明确两件事情。 1.join ... -
Java 理论与实践: 应用 fork-join 框架,第 2 部分
2011-10-21 10:40 773在 上一期 Java 理论与实践 中,我们研究了 fork ... -
模式匹配算法详解:KMP算法 .
2011-10-15 17:22 844基本的模式匹配算法 假设现在有主串S=s1,...,sn,, ... -
hashmap线程不安全在哪里?
2011-10-13 11:33 1111大家都知道HashMap不是线程安全的,但是大家的理解 ...
相关推荐
Java并发Fork-Join框架原理 Java并发Fork-Join框架原理是Java7中提供的一种并行执行任务的框架,旨在提高程序的执行效率和性能。该框架的核心思想是将大任务分割成若干个小任务,并将其分配给不同的线程执行,以...
Java 语言从一开始能够支持线程和并发性;该语言包括像 synchronized 和 volatile 这样的同步原语,而类库包含像 Thread 这样的类。然而,1995 年流行的并发原语反映了当时的硬件现状:大多数商用系统根本没有提供...
fork/join框架是ExecutorService接口的一个实现,可以帮助开发人员充分利用多核处理器的优势,编写出并行执行的程序,提高应用程序的性能;设计的目的是为了处理那些可以被递归拆分的任务。
Fork/Join框架是Java7引入的一种用于并行任务执行的框架,它允许将复杂任务拆分成多个子任务,并行执行,然后通过join操作将结果聚合。Fork/Join框架特别适合处理可以递归拆分的计算密集型任务,比如大数据集的搜索...
Go-Golang版的Fork-Join框架是一种在Golang中实现的并发编程模型,灵感来源于Java的ForkJoin框架。这个框架的核心理念是通过将大任务分解为小任务,然后并行执行这些小任务,最后合并结果,以提高计算效率。在Golang...
ForkJoin框架 是Java 7中引入的,旨在进一步提高并发程序的性能。它使用了一种称为“工作窃取”的算法,允许线程动态地重分配任务。ForkJoin的核心思想是将大任务分解为更小的任务,然后并行处理这些任务,最后合并...
在Java并发编程中,Fork/Join框架是一个强大的工具,尤其在处理大量数据时能显著提升性能。这个框架从Java 7开始引入,是ExecutorService的一个实现,它基于分而治之的策略,将大任务分解成多个小任务,然后并行地...
Java的Fork/Join框架是一种用于并行计算的框架,它基于分治法的原理,将大任务分解成小任务并行执行,最后再将结果合并。这种框架特别适合于可以分解为多个子任务且子任务可以并行处理的场景。本文将详细介绍Fork/...
本文将简要回顾Java中的并发编程基础知识,介绍java.util.concurrent包提供的高级并发原语,并深入探讨Fork/Join框架及其在Java SE 7中的应用。 首先,让我们回顾一下Java中基本的并发机制。自Java早期版本起,线程...
1. **Fork/Join框架概述**:这是Java 7引入的一个框架,用于解决大问题的分解和并行计算。它将复杂的问题分解为更小的子问题,然后通过并行执行这些子问题来加速求解。Fork/Join框架的核心是`ForkJoinPool`和`...
Fork/Join框架是Java并发库中的一部分,自Java 7开始引入,它为开发者提供了一种高效的处理大规模计算任务的方法。这个框架基于分治策略,将大任务分解成若干小任务,然后并行执行这些小任务,最后再将结果合并。...
ForkJoin框架是Java并发编程中的一个重要工具,它基于分治策略,旨在高效处理大量数据。框架的核心思想是将一个大型任务分解成多个小型任务,然后通过并行执行这些子任务来提高处理效率。ForkJoin框架在Hadoop ...
ForkJoin并发框架是Java 7引入的一种高效并行计算框架,它基于分而治之(Divide and Conquer)的策略,适用于处理大量可分割的任务。...理解并熟练掌握ForkJoin框架,对于优化Java应用程序的性能至关重要。
全网第一篇通过图文介绍Fork/Join框架与CompleteableFuture的PPT
在Java编程领域,Fork/Join框架是一种并行计算模型,设计用于高效处理大量数据,尤其是在多核处理器系统上。这个框架是Java 7引入的一个重要特性,它基于分而治之(Divide and Conquer)策略,将复杂任务拆分为更小...
Java ForkJoin框架的原理及用法 Java ForkJoin框架是Java 1.7后提供的一种多线并发处理框架,主要思想是分而治之,将复杂的计算按照设定的阈值进行分解成多个计算,然后将各个计算结果进行汇总。ForkJoin框架的使用...
在Java编程语言中,ForkJoin框架是一种并行计算模型,设计用于高效处理大量可分割的任务。这个模型基于“分而治之”(Divide and Conquer)的策略,允许程序将一个大任务拆分为若干小任务,这些小任务可以并行执行,...
Fork/Join框架是Java并发处理的一个重要工具,它基于工作窃取算法,设计用于高效地执行并行计算任务。这个框架是Java 7引入的,位于`java.util.concurrent.fork/join`包中,目的是简化多核处理器环境下大规模数据...