Author Archive

[译]深入 NGINX: 为性能和扩展所做之设计

原文链接:Inside NGINX: How We Designed for Performance & Scale 翻译:丁一

NGINX在web性能上的表现尤为出众,这完全得益于其设计方式,许多web和应用服务器都是基于线程或进程这种简单的架构,NGINX用了一种精妙的事件驱动架构,在现代的硬件上,它可以处理成千上万的并发连接。

Inside NGINX中的信息图对高级别的进程架构和NGINX如何在单个进程中处理多个连接进行了深入探讨。本文更进一步地阐述了NGINX的所有工作原理。

Read more

JSR133中文版

原文链接  译文链接   翻译:丁一   下载:JSR133中文版 firefox_old_school_final

本文是JSR-133规范,即JavaTM内存模型与线程规范,由JSR-133专家组开发。本规范是JSR-176(定义了JavaTM平台 Tiger(5.0)发布版的主要特性)的一部分。本规范的标准内容将合并到JavaTM语言规范JavaTM虚拟机规范以及java.lang包的类说明中。本JSR-133规范将不再通过JCP维护和修改。未来所有对这些标准化内容的更新、修正以及说明都会出现在上述这些文档中。

本规范的标准化内容包含在第5, 7, 9.2, 9.3, 11, 12, 14, 15以及16节。其它章节,以及上述提到的章节的部分内容,属非标准化内容,用于解释和说明标准化内容。如果标准化内容和非标准化内容有冲突,以标准化内容为准。

本规范的讨论与开发异常复杂且专业性强,需要对一些学术论题有深刻的见解并了解它们的发展过程。这些讨论在JMM web站点上都有存档。该站点提供了额外的信息,可以帮助理解本规范形成的过程。
Read more

happens-before俗解

学习Java并发,到后面总会接触到happens-before偏序关系。初接触玩意儿简直就是不知所云,下面是经过一段时间折腾后个人对此的一点浅薄理解,希望对初接触的人有帮助。如有不正确之处,欢迎指正。

synchronized、大部分锁,众所周知的一个功能就是使多个线程互斥/串行的(共享锁允许多个线程同时访问,如读锁)访问临界区,但他们的第二个功能 —— 保证变量的可见性 —— 常被遗忘。

为什么存在可见性问题?简单介绍下。相对于内存,CPU的速度是极高的,如果CPU需要存取数据时都直接与内存打交道,在存取过程中,CPU将一直空闲,这是一种极大的浪费,妈妈说,浪费是不好的,所以,现代的CPU里都有很多寄存器,多级cache,他们比内存的存取速度高多了。某个线程执行时,内存中的一份数据,会存在于该线程的工作存储中(working memory,是cache和寄存器的一个抽象,这个解释源于《Concurrent Programming in Java: Design Principles and Patterns, Second Edition》§2.2.7,原文:Every thread is defined to have a working memory (an abstraction of caches and registers) in which to store values. 有不少人觉得working memory是内存的某个部分,这可能是有些译作将working memory译为工作内存的缘故,为避免混淆,这里称其为工作存储,每个线程都有自己的工作存储),并在某个特定时候回写到内存。单线程时,这没有问题,如果是多线程要同时访问同一个变量呢?内存中一个变量会存在于多个工作存储中,线程1修改了变量a的值什么时候对线程2可见?此外,编译器或运行时为了效率可以在允许的时候对指令进行重排序,重排序后的执行顺序就与代码不一致了,这样线程2读取某个变量的时候线程1可能还没有进行写入操作呢,虽然代码顺序上写操作是在前面的。这就是可见性问题的由来。

Read more

软件事务内存导论(十)处理写偏斜异常

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

处理写偏斜异常

在6.6节中,我们曾经简单讨论了写偏斜(write skew)以及Clojure STM是如何解决这个问题的。Akka同样提供了处理写偏斜问题的支持,但是需要我们配置一下才能生效。OK,一听到配置这个词可能让你觉得有些提心吊胆,但实际操作起来其实起来还是蛮简单的。下面就让我们首先了解一下Akka在不进行任何配置情况下的默认行为。

Read more

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

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

集合与事务

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

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

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

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

Read more

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

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

提交和回滚事件

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

Read more

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

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

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

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

Read more

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

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

配置Akka事务

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

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

Read more

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

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

创建事务

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

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

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

Read more

软件事务内存导论(三)用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的例子。

Read more

详细分析Java中断机制

本文是作者原创,首发于InfoQ:http://www.infoq.com/cn/articles/java-interrupt-mechanism

1. 引言

当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务。Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制。

如果对Java中断没有一个全面的了解,可能会误以为被中断的线程将立马退出运行,但事实并非如此。中断机制是如何工作的?捕获或检测到中断后,是抛出InterruptedException还是重设中断状态以及在方法中吞掉中断状态会有什么后果?Thread.stop与中断相比又有哪些异同?什么情况下需要使用中断?本文将从以上几个方面进行描述。

Read more

有关“双重检查锁定失效”的说明

原文地址 译者:丁一

双重检查锁定(以下称为DCL)已被广泛当做多线程环境下延迟初始化的一种高效手段。

遗憾的是,在Java中,如果没有额外的同步,它并不可靠。在其它语言中,如c++,实现DCL,需要依赖于处理器的内存模型、编译器实行的重排序以及编译器与同步库之间的交互。由于c++没有对这些做出明确规定,很难说DCL是否有效。可以在c++中使用显式的内存屏障来使DCL生效,但Java中并没有这些屏障。

Read more

剖析同步器

原文链接 作者:Jakob Jenkov 译者:丁一

虽然许多同步器(如锁,信号量,阻塞队列等)功能上各不相同,但它们的内部设计上却差别不大。换句话说,它们内部的的基础部分是相同(或相似)的。了解这些基础部件能在设计同步器的时候给我们大大的帮助。这就是本文要细说的内容。

注:本文的内容是哥本哈根信息技术大学一个由Jakob Jenkov,Toke Johansen和Lars Bjørn参与的M.Sc.学生项目的部分成果。在此项目期间我们咨询Doug Lea是否知道类似的研究。有趣的是在开发Java 5并发工具包期间他已经提出了类似的结论。Doug Lea的研究,我相信,在《Java Concurrency in Practice》一书中有描述。这本书有一章“剖析同步器”就类似于本文,但不尽相同。

大部分同步器都是用来保护某个区域(临界区)的代码,这些代码可能会被多线程并发访问。要实现这个目标,同步器一般要支持下列功能:

  1. 状态
  2. 访问条件
  3. 状态变化
  4. 通知策略
  5. Test-and-Set方法
  6. Set方法

并不是所有同步器都包含上述部分,也有些并不完全遵照上面的内容。但通常你能从中发现这些部分的一或多个。

Read more

为什么ConcurrentHashMap是弱一致的

本文将用到Java内存模型的happens-before偏序关系(下文将简称为hb)以及ConcurrentHashMap的底层模型相关的知识。happens-before相关内容参见:JLS §17.4.5. Happens-before Order深入理解Java内存模型以及Happens before;ConcurrentHashMap的详细介绍以及底层原理见深入分析ConcurrentHashMap。本文将从ConcurrentHashMap的get,clear,iterator(entrySet、keySet、values方法)三个方法来分析它们的弱一致问题。

Read more

类中字段赋值给局部变量后再使用意义何在?

Concurrency-interest邮件列表中有人问了这么一个问题:ArrayBlockingQueue中有个对象字段lock,在ArrayBlockingQueue的很多方法中,使用这个lock时都将其先赋值给一个局部变量,然后再通过局部变量调用lock上的方法,而没有直接使用lock字段,如remainingCapacity方法中先将this.lock赋值给一个局部变量lock,然后再使用这个局部变量:

public class ArrayBlockingQueue {
	private final ReentrantLock lock;
	
	//...other fields and methods
	
	public int remainingCapacity() {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			return items.length - count;
		} finally {
			lock.unlock();
		}
	}
}

而不是像这样直接使用类中的字段:

public class ArrayBlockingQueue {
	private final ReentrantLock lock;
	
	//...other fields and methods
	
	public int remainingCapacity() {
		this.lock.lock();
		try {
			return items.length - count;
		} finally {
			this.lock.unlock();
		}
	}
}

那么为什么要这么做,有什么理由或说法?

Read more

return top