Java中不同的并发实现的性能比较
原文链接 作者:Alex Zhitnitsky 译者:有孚
Fork/Join框架在不同配置下的表现如何?
正如即将上映的星球大战那样,Java 8的并行流也是毁誉参半。并行流(Parallel Stream)的语法糖就像预告片里的新型光剑一样令人兴奋不已。现在Java中实现并发编程存在多种方式,我们希望了解这么做所带来的性能提升及风险是什么。从经过260多次测试之后拿到的数据来看,还是增加了不少新的见解的,这里我们想和大家分享一下。
正如即将上映的星球大战那样,Java 8的并行流也是毁誉参半。并行流(Parallel Stream)的语法糖就像预告片里的新型光剑一样令人兴奋不已。现在Java中实现并发编程存在多种方式,我们希望了解这么做所带来的性能提升及风险是什么。从经过260多次测试之后拿到的数据来看,还是增加了不少新的见解的,这里我们想和大家分享一下。
声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
问题
我们希望事件能在预定的时间内到达,即使事件不到达,也要确保程序能及时进行响应。
通常此类事件是单一的异步操作(例如,等待 Web 服务请求的响应)。
解决方案
Timeout 操 作 符 在 输 入 流 上 建 立 一 个 可 调 节 的 超 时 窗 口。 一 旦 新 的 事 件 到 达, 就 重 置 超 时 窗 口。 如 果 超 过 期 限 后 事 件 仍 没 到 达,Timeout 操 作 符 就 结 束 流, 并 产 生 一 个 包 含 TimeoutException 的 OnError 通知。
声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
问题
有时事件来得太快,这是编写响应式代码时经常碰到的问题。一个速度太快的事件流可导
致程序的处理过程崩溃。
解决方案
Rx 专门提供了几个操作符,用来对付大量涌现的事件数据。Throttle 和 Sample 这两个操 作符提供了两种不同方法来抑制快速涌来的输入事件。
声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
问题
有一系列事件,需要在它们到达时进行分组。举个例子,需要对一些成对的输入作出响
应。第二个例子,需要在 2 秒钟的窗口期内,对所有输入进行响应。
声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
问题
Rx 尽量做到了线程不可知(thread agnostic)。因此它会在任意一个活动线程中发出通知(例如 OnNext)。
但是我们通常希望通知只发给特定的上下文。例如 UI 元素只能被它所属的 UI 线程控制, 因此,如果要根据 Rx 的通知来修改 UI,就应该把通知“转移”到 UI 线程。
声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
问题
把一个事件作为 Rx 输入流,每次事件发生时通过 OnNext 生成数据。
解决方案
Observable 类 定 义 了 一 些 事 件 转 换 器。 大 部 分 .NET 框 架 事 件 与 FromEventPattern 兼 容, 对于不遵循通用模式的事件,需要改用 FromEvent。
FromEventPattern 最适合使用委托类型为 EventHandler 的事件。很多较新框架类的事 件都采用了这种委托类型。例如,Progress 类定义了事件 ProgressChanged,这个事件 的委托类型就是 EventHandler,因此,它就很容易被封装到 FromEventPattern:
声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
LINQ 是 对 序 列 数 据 进 行 查 询 的 一 系 列 语 言 功 能。 内 置 的 LINQ to Objects( 基 于 IEnumerable<T>) 和 LINQ to Entities( 基 于 IQueryable<T>) 是 两 个 最 常 用 的 LINQ 提 供 者。另外还有很多提供者,并且大多数都采用相同的基本架构。查询是延后执行(lazily evaluated)的,只有在需要时才会从序列中获取数据。从概念上讲,这是一种拉取模式。 在查询过程中数据项是被逐个拉取出来的。
Reactive Extensions(Rx)把事件看作是依次到达的数据序列。因此,将 Rx 认作是 LINQ to events( 基 于 IObservable<T>) 也 是 可 以 的, 它 与 其 他 LINQ 提 供 者 的 主 要 区 别 在 于, Rx 采用“推送”模式。就是说,Rx 的查询规定了在事件到达时程序该如何响应。Rx 在 LINQ 的基础上构建,增加了一些功能强大的操作符,作为扩展方法。
作者:Coding 架构师 王振威
引子:我接触过很多编程语言,接触过各种各样的服务器端开发,Java,Go,Ruby,Javascript等语言,Spring,Node.js,Rails等等常见服务器端框架和编程模型都有接触。这里谈一下我个人对高性能服务器端程序的一些看法,希望给各位读者一些认识。这片文章提到的内容也是 Coding(https://coding.net) 代码托管乃至整站都在使用的一些概念和技术。
此外,阅读这篇文章,有如下几个前提:不谈硬件,不评论编程语言以及框架的好坏,不谈高级算法,可拍砖,拒绝喷子。
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
我们在本章前面的部分中对Go语言提供的各种传统同步工具和方法进行了逐一的介绍。在本节,我们将运用它们来构造一个并发安全的字典(Map)类型。
我们已经知道,Go语言提供的字典类型并不是并发安全的。因此,我们需要使用一些同步方法对它进行扩展。这看起来并不困难。我们只要使用读写锁将针对一个字典类型值的读操作和写操作保护起来就可以了。确实,读写锁应该是我们首先想到的同步工具。不过,我们还不能确定只使用它是否就足够了。不管怎样,让我们先来编写并发安全的字典类型的第一个版本。
我们先来确定并发安全的字典类型的行为。还记得吗?依然,这需要声明一个接口类型。我们在第4章带领读者编写过OrderedMap接口类型及其实现类型。我们可以借鉴OrderedMap接口类型的声明并编写出需要在这里声明的接口类型ConcurrentMap。实际上,ConcurrentMap接口类型的方法集合应该是OrderedMap接口类型的方法集合的一个子集。我们只需从OrderedMap中去除那些代表有序Map特有行为的方法声明即可。既然是这样,我何不从这两个自定义的字典接口类型中抽出一个公共接口呢?
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
本章要讲解的是sync.Pool类型。我们可以把sync.Pool类型值看作是存放可被重复使用的值的容器。此类容器是自动伸缩的、高效的,同时也是并发安全的。为了描述方便,我们也会把sync.Pool类型的值称为临时对象池,而把存于其中的值称为对象值。至于为什么要加“临时“这两个字,我们稍后再解释。
我们在用复合字面量初始化一个临时对象池的时候可以为它唯一的公开字段New赋值。该字段的类型是func() interface{},即一个函数类型。可以猜到,被赋给字段New的函数会被临时对象池用来创建对象值。不过,实际上,该函数几乎仅在池中无可用对象值的时候才会被调用。
类型sync.Pool有两个公开的方法。一个是Get,另一个是Put。前者的功能是从池中获取一个interface{}类型的值,而后者的作用则是把一个interface{}类型的值放置于池中。
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
我们在第6章多次提到过sync.WaitGroup类型和它的方法。sync.WaitGroup类型的值也是开箱即用的。例如,在声明
[code lang=”java”]
var wg sync.WaitGroup
[/code]
之后,我们就可以直接正常使用wg变量了。该类型有三个指针方法,即Add、Done和Wait。
类型sync.WaitGroup是一个结构体类型。在它之中有一个代表计数的字段。当一个sync.WaitGroup类型的变量被声明之后,其值中的那个计数值将会是0。我们可以通过该值的Add方法增大或减少其中的计数值。例如:
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
现在,让我们再次聚焦到sync代码包。除了我们介绍过的互斥锁、读写锁和条件变量,该代码包还为我们提供了几个非常有用的API。其中一个比较有特色的就是结构体类型sync.Once和它的Do方法。
与代表锁的结构体类型sync.Mutex和sync.RWMutex一样,sync.Once也是开箱即用的。换句话说,我们仅需对它进行简单的声明即可使用,就像这样:
[code lang=”java”]
var once sync.Once
once.Do(func() { fmt.Println("Once!") })
[/code]
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
我们已经知道,原子操作即是进行过程中不能被中断的操作。也就是说,针对某个值的原子操作在被进行的过程当中,CPU绝不会再去进行其它的针对该值的操作。无论这些其它的操作是否为原子操作都会是这样。为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。只有这样才能够在并发环境下保证原子操作的绝对安全。
Go语言提供的原子操作都是非侵入式的。它们由标准库代码包sync/atomic中的众多函数代表。我们可以通过调用这些函数对几种简单的类型的值进行原子操作。这些类型包括int32、int64、uint32、uint64、uintptr和unsafe.Pointer类型,共6个。这些函数提供的原子操作共有5种,即:增或减、比较并交换、载入、存储和交换。它们分别提供了不同的功能,且适用的场景也有所区别。下面,我们就根据这些种类对Go语言提供的原子操作进行逐一的讲解。
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
我们在第6章讲多线程编程的时候详细说明过条件变量的概念、原理和适用场景。因此,我们在本小节仅对sync代码包中与条件变量相关的API进行简单的介绍,并使用它们来改造我们之前实现的*myDataFile类型的相关方法。
在Go语言中,sync.Cond类型代表了条件变量。与互斥锁和读写锁不同,简单的声明无法创建出一个可用的条件变量。为了得到这样一个条件变量,我们需要用到sync.NewCond函数。该函数的声明如下:
[code lang=”java”]
func NewCond(l Locker) *Cond
[/code]
声明:本文是《Go并发编程实战》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但却没有提到过读写锁。这两种锁对于传统的并发程序来说都是非常常用和重要的。
互斥锁是传统的并发程序对共享资源进行访问控制的主要手段。它由标准库代码包sync中的Mutex结构体类型代表。sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开方法——Lock和Unlock。顾名思义,前者被用于锁定当前的互斥量,而后者则被用来对当前的互斥量进行解锁。
类型sync.Mutex的零值表示了未被锁定的互斥量。也就是说,它是一个开箱即用的工具。我们只需对它进行简单声明就可以正常使用了,就像这样: