软件事务内存导论(九) 集合与事务

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

集合与事务

在我们努力学习这些示例的过程中,很容易就会忘记我们所要处理的值都必须是不可变的。只有实体才是可变的,而状态值则是不可变的。虽然STM已经为我们减轻了很多负担,但如果想要在维护不可变性的同时还要兼顾性能的话,对我们来说也将是一个非常严峻的挑战。

为了保证不可变性,我们采取的第一个步骤是将单纯用来保存数据的类(value classes)及其内部所有成员字段都置为final(在Scala中是val)。然后,我们需要传递地保证我们自己定义的类里面的字段所使用的类也都是不可变的。可以说,将字段和类的定义置为final这一步是整个过程的基础,这同时也是避免并发问题的第一步。

虽说不可变性可以使代码变得又好又安全,但是由于性能问题,程序员们还是不大愿意使用这一特性。其症结在于,为了维护不可变性,我们可能在数据没发生任何变动的情况下也要进行拷贝操作,而这种无谓的拷贝对性能伤害很大。为了解决这个问题,我们在3.6节中曾经讨论过持久化数据结构以及如何使用这类数据结构来减轻程序在性能方面的负担。而在持久化数据结构的实现方面,已经有很多现成的第三方库可供使用,而Scala本身也提供了这类数据结构。由于Java也有实现好的持久化数据结构可用,所以我们就无需专门为使用这个特性而去换用自己不熟悉的语言。

除了不可变性之外,我们还希望能获得一些事务运行所需要的数据结构——这些数据结构的值是不可变的,但其实体可以在托管事务中被改变。Akka提供了两种托管数据结构——TransactionalVector和TransactionalMap。这两种数据结构源自于高效的Scala数据结构,其工作原理和Java的list、map类似。下面就让我们一起来学习如何在Java和Scala中使用TransactionalMap

阅读全文

软件事务内存导论(八)提交和回滚事件

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

提交和回滚事件

Java的try-catch-finally语法结构不但使我们可以安全地处理异常,还能够在程序抛出异常时选择性地执行一些代码。同样地,我们也可以控制程序在事务成功提交之后去执行某段代码,而当事务回滚时则去执行另一段代码。StmUtils中的deferred()和compensatiing()这两个函数分别提供了上述功能。特别地,在实现事务的过程中,为保证事务能顺利完成,我们通常会加入一些带副作用的逻辑,而deferred()函数则是一个执行所有这部分逻辑的绝佳地点。

阅读全文

Java 7 Concurrency Cookbook – Javier Fernández González -前言

原文下载链接      作者: Javier Fernández González     译者:郑玉婷

前言

当你用计算机工作的时候,你在同时做多样事情。你可以边听音乐边写文档边读取邮件。你可以这样做的原因是你的操作系统运行并发任务。并发编程是关于基础与进程的一个 提供多任务或者程序同时运行还相互沟通来交换数据和相互同步的平台。Java是一个并发平台,在Java程序中提供很多执行并发任务的类。每个版本,Java提升了促进程序员开发并发程序的功能。这本书包含了在Java版本7的并发API中最重要和最有用的技巧,所以你可以直接在你的应用程序中使用的,以下这些:

• 基本线程管理
• 线程同步机制
• 执行者代表的线程的创建与管理
• Fork/Join框架来提高应用程序的性能
• 并发程序的数据结构
• 按照需要来调整一些并发类的默认行为
• 测试Java的并发应用程序

阅读全文

软件事务内存导论(七)阻塞事务

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

阻塞事务——有意识地等待

我们经常会遇到这样一种情况,即某事务T能否成功完成依赖于某个变量是否发生了变化,并且由于这种原因所引起的事务运行失败也可能只是暂时性的。作为对这种暂时性失败的响应,我们可能会返回一个错误码并告诉事务T等待一段时间之后再重试。然而在事务T等待期间,即使其他任务已经更改了事务T所依赖的数据,事务T也没法立即感知到并重试了。为了解决这一问题,Akka为我们提供了一个简单的工具——retry(),该函数可以先将事务进行回滚,并将事务置为阻塞状态直到该事物所依赖的引用对象发生变化或事务阻塞的时间超过了之前配置的阻塞超时为止。我本人更愿意将这一过程称为“有意识地等待”,因为这种说法听起来比“阻塞”更合适一些。下面让我们将阻塞(或有意识地等待)用于下面的两个例子当中。

阅读全文

非阻塞同步算法实战(一)

感谢trytocache投递本文。

前言

本文写给对ConcurrentLinkedQueue的实现和非阻塞同步算法的实现原理有一定了解,但缺少实践经验的朋友,文中包括了实战中的尝试、所走的弯路,经验和教训。

背景介绍

上个月,我被安排独自负责一个聊天系统的服务端,因为一些原因,我没使用现成的开源框架,网络那块直接使用AIO,收数据时,因为只会从channel里过来,所以不需要考虑同步问题;但是发送数据时,因为有聊天消息的转发,所以必需处理这个同步问题。AIO中,是处理完一个注册的操作后,再执行我们定义的方法,此时,如果还有数据需要写,则继续注册写操作,如果没有则不注册;提交数据时,如果当前没有注册写操作,则注册一个,否则仅提交(此时再注册则会报异常)。这样,需要同步的点就是:如何知道当前还有没有数据需要发送(因为其它线程也可以提交数据到此处),和如何知道此次提交时,有没有注册写操作。总之,要完成:有数据要发送时,必需有写操作被注册,并且只能注册一次;没有数据时,不能有写操作被注册。 阅读全文

Java并发中正确使用volatile

作者:一粟   整理和翻译自Twitter实时搜索的PPT

前几天并发编程群里有同学对volatile的用法提出了疑问,刚好我记得Twitter有关实时搜索的这个PPT对这个问题解释的很清晰并有一个实际的应用场景,于是周末把这个问题摘录了一些和并发相关的内容如下: 阅读全文

软件事务内存导论(六)配置Akka事务

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

配置Akka事务

默认情况下,Akka为其相关的运行参数都设定了默认值,我们可以通过代码或配置文件akka.conf来更改这些默认设置。如果想了解如何指定或修改该配置文件位置的详细信息,请参阅Akka的文档。

针对单个事务,我们可以利用TransactionFactory在程序代码中更改其设置。下面就让我们用这种方式先后在Java和Scala中更改一些设置来为你展示如何实现设置的变更。

阅读全文

深入理解并行编程-分割和同步设计(二)

原文链接    作者:paul    译者:谢宝友,鲁阳,陈渝

双端队列是一种元素可以从两端插入或删除的数据结构[Knu73]。据说实现一种基于锁的允许在双端队列的两端进行并发操作的方法非常困难[Gro07]。本节将展示一种分割设计策略,能实现合理且简单的解决方案,请看下面的小节中的三种通用方法。

阅读全文

C1X 系列 : 多线程 (N1494)

感谢林永听投递本文。  校对:方腾飞

1. 关于 C1X 标准

C1X 是 C 语言的下一个标准,用于取代现有的 C99 标准。 C1X 是一个非正式名字,该标准仍在制订中,最新的工作草案是 N1494 ,发布于 2010 年 6 月。与 C99 相比, C1X 在语言和库上有显著的变化,本文重点分析 N1494 草案中的多线程部分。

2. 呼之欲出的多线程

不瞒你说, C99 标准里面的内存模型仍然是单线程的,即所有代码都运行在一个线程(进程)内。也许,你简直不敢相信这个是真的,因为说不定你每天都与多线程打交道, 使用 _beginthread , CreateThread 或 pthread_create 等不同平台的函数去创建线程。当你担心每个变量被编译器优化时,不得不加上 volatile 修饰符时,或者加上内存栅栏 (memory barrier) ,都印证了 C99 为单线程的内存模型。

32 位保护模式的出现,催生了多任务操作系统的诞生,随之而来的就是多线程环境。随着多核时代的到来,多线程环境成为程序开发不可逃避的问题。无锁编程,并行编程等已经成熟的技术在 C 社区里,常常给初学者遥不可及的感觉。在迫切需要多线程的时代,不同厂商和平台都纷纷开发了各自的多线程库,如 POSIX 的 pthreads 和 window 的 winThread 。软件工程师在特定的平台下,使用各自的线程库来开发多线程或并发程序,并非很难,但可移植却成了他们面临的问题。如 pthreads 在默认情况下,互斥锁是非递归的,要使用递归互斥锁,程序库必须支持一些扩展 feature ;而 window 下的锁默认情况下是递归的。 lock-wait-wakup 方式也不尽然相同,这对开发跨平台的多线程程序来说无异于雪上加霜。

放眼 C 语言的后来者,如 Java ,很早就支持了多线程,并且它的内存模型在不断地修改,以适应发挥多线程的优势。 Erlang 作为一种天生的并发编程语言,踏上编程语言的行列,成为并发编程的新星。同时,很多脚本语都内置地支持线程类或结构。 C/C++ 作为后来加入到多线程的行列,尽管已经太晚了,但对于 C 语言社区来说,这无疑是最令人兴奋的消息了。

阅读全文

软件事务内存导论(五)创建嵌套事务

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

1.1    创建嵌套事务

在之前的示例中,每个用到事务的方法都是各自在其内部单独创建事务,并且事务所涉及的变动也都是各自独立提交的。但如果我们想要将多个方法里的事务调整成一个统一的原子操作的时候,上述做法就无能为力了,所以我们需要使用嵌套事务来实现这一目标。

通过使用嵌套事务,所有被主控函数调用的那些函数所创建的事务都会默认被整合到主控函数的事务中。除此之外,Akka/Multiverse还提供了很多其他配置选项,如新隔离事务(new isolated transactions)等。总之,使用了嵌套事务之后,只有位于最外层的主控函数事务提交时,其内部所做的变更才会被提交。在具体使用时,为了保证所有嵌套事务能够作为一个整体成功完成,我们需要保证所有函数都必须在一个可配置的超时范围内做完。
阅读全文

为代码签名,供后人瞻仰或唾弃,你敢吗?

如何衡量代码质量的好坏,是否有一个标准,是否可以量化?

我认为答案是否定的。如果今年中央给各省下个死命令,要求年度GDP增长达到10%,我相信每个省一定都能完成任务。这几年,GDP增长都在8%以上,CPI增长不到4%,民族复兴完成了62%,这些都量化的,你是否满意?

回到开发的问题上来,有一些数字,比如bug的个数,reopen的次数能说明一定的问题,但不是全部。它只能描述系统的外在质量的一部分,这个外在质量可以由QA来保证。但是内部质量只能靠开发自己来保证,牺牲内部质量来保证功能和外在质量是不应该的。 阅读全文

深入理解并行编程-分割和同步设计(一)

原文链接     作者:paul    译者:谢宝友,鲁阳,陈渝

在商用计算机中,多核系统已经越来越常见了,本章将描述如何设计能更好利用多核优势的软件。我们将介绍一些习语,或者叫“设计模式”,来帮助您权衡性能、可扩展性和响应时间。在上一章我们说过,您在编写并行软件时最重要的考虑是如何进行分割。正确的分割问题能够让解决办法简单、可扩展并且高性能,而不恰当的分割问题则会产生缓慢且复杂的解决方案。
阅读全文

软件事务内存导论(四)创建事务

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

创建事务

我们创建事务的目的是为了协调针对多个托管引用的变更。事务将会保证这些变更是原子的,也就是说,所有的托管引用要么全部被提交要么全部被丢弃,所以在事务之外我们将不会看到有任何局部变更(partial changes)出现。此外,我们也可以用创建事务的方式来解决对单个ref先读后写所引发的相关问题。

Akka是用Scala开发出来的,所以如果我们工作中用的是Scala的话,就可以直接幸福地享用Akka简洁明了的API了。对于那些日常工作中不能使用Scala开发的程序员,Akka同样也提供了一组方便的API,以帮助他们通过Java语言来使用该类库的功能。本节我们将会看到如何利用Akka在Java和Scala中创建事务。

首先我们需要选一个适合用事务来解决的例子。我们在第5章中重构的EnergySource类使用了显式的加锁和解锁操作(其最终版本详见5.7节),下面让就我们将这些显式的加锁/解锁操作换用Akka的事务API来实现。

阅读全文

软件事务内存导论(三)用Akka/Multiverse STM实现并发

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

用Akka/Multiverse STM实现并发

上面我们已经学习了如何在Clojure里使用STM,我猜你现在一定很好奇如何在Java代码中使用STM。而对于这一需求,我们有如下选择:

  • 直接在Java中使用Clojure STM。方法非常简单,我们只需将事务的代码封装在一个Callable接口的实现中就行了,详情请参见第7章。
  • 喜欢用注解(annotation)的开发者可能会更倾向于使用Multiverse的STM API.
  • 除了STM之外,如果我们计划使用角色(actor),那么还可以考虑选择Akka库。

Multiverse是由Peter Veentjer主持开发的一个基于Java的STM实现。通过这个库,我们可以在Java代码中使用注解来标识事务边界。我们既可以用@TransactionalMethod注解将单个的方法标记为事务性的,也可以用@TransactionalObject注解将一个类的所有方法都标记为事务性的。为了与其他JVM上的语言进行集成,Multiverse还提供了一组丰富的API来控制事物的开始和结束。

Akka是一个由Jonas Boner主持开发的一个基于Scala的解决方案,该方案可以用于包括Java在内的很多其他运行于JVM上的语言。Akka不但提供了STM和基于角色(actor)的并发方案,还提供了将二者混合使用的选项。此外,Akka使用Multiverse作为其STM的实现并提供了ACI(ACID的子集)特性。

Akka的性能非常棒,并且由于它既支持STM又支持基于角色(actor)的模型(详情请参见第8章),本章我们将会用它来实现演示Java STM的例子。

阅读全文

软件事务内存导论(二)软件事务内存

声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。

1.1    软件事务内存

将实体与状态分离的做法有助于STM(软件事务内存)解决与同步相关的两大主要问题:跨越内存栅栏和避免竞争条件。让我们先来看一下在Clojure上下文中的STM是什么样子,然后再在Java里面使用它。

通过将对内存的访问封装在事务(transactions)中,Clojure消除了内存同步过程中我们易犯的那些错误(见《Programming Clojure》[Hal09]和《The Joy of Clojure》[FH11])。Clojure会敏锐地观察和协调线程的所有活动。如果没有任何冲突——例如,每个线程都在存取不同账户——则整个过程中就不会涉及到任何锁,于是也就不会有延迟,并最终达到最大的并发度。当有两个线程试图访问相同数据时,事务管理器就会介入解决冲突,而我们的代码也就无需涉及任何显式加锁的操作。下面让我们一起研究一下这套系统是如何运作的。

在设计上,值(values)是不可变的,而实体(identities)也仅在Clojure的事务中是可变的。在Clojure中,压根就没有改变状态的方法,也没有任何相关的编程工具可用。而如果出现任何试图在事务之外改变实体的动作时,系统就会抛出illegalStateException异常。换句话说,一旦与事务进行了绑定,在没有冲突时,所有变更都是即时生效的;而一旦发生冲突,Clojure将会自动将事务回滚并重试。我们程序员的主要职责是保证事务中的代码都是幂等的——这是我们在函数式编程中避免副作用的常用手段,而这种手段在Clojure的编程模型中也同样适用。 阅读全文

return top