Java 6中的线程优化真的有效么?

介绍 — Java 6中的线程优化

Sun、IBM、BEA和其他公司在各自实现的Java 6虚拟机上都花费了大量的精力 优化锁的管理和同步。诸如偏向锁(biased locking)、锁粗化(lock coarsening)、由逸出(escape)分析产生的锁省略、自适应自旋锁(adaptive spinning)这些特性,都是通过在应用程序线程之间更高效地共享数据,从而提 高并发效率。尽管这些特性都是成熟且有趣的,但是问题在于:它们的承诺真的 能实现么?在这篇由两部分组成的文章里,我将逐一探究这些特性,并尝试在单 一线程基准的协助下,回答关于性能的问题。

悲观锁模型

Java支持的锁模型绝对是悲观锁(其实,大多数线程库都是如此)。如果有两 个或者更多线程使用数据时会彼此干扰,这种极小的风险也会强迫我们采用非常 严厉的手段防止这种情况的发生——使用锁。然而研究表明,锁很少被占用。也 就是说,一个访问锁的线程很少必须等待来获取它。但是请求锁的动作将会触发 一系列的动作,这可能导致严重的系统开销,这是不可避免的。

我们的确还有其他的选择。举例来说,考虑一下线程安全的StringBuffer的用 法。问问你自己:是否你曾经明知道它只能被一个线程安全地访问,还是坚持使 用StringBuffer,为什么不用StringBuilder代替呢?

知道大多数的锁都不存在竞争,或者很少存在竞争的事实对我们作用并不大, 因为即使是两个线程访问相同数据的概率非常低,也会强迫我们使用锁,通过同 步来保护被访问的数据。“我们真的需要锁么?”这个问题只有在我们将锁放在 运行时环境的上下文中观察之后,才能最终给出答案。为了找到问题的答案,JVM 的开发者已经开始在HotSpot和JIT上进行了很多的实验性的工作。现在,我们已 经从这些工作中获得了自适应自旋锁、偏向锁和以及两种方式的锁消除(lock elimination)——锁粗化和锁省略(lock elision)。在我们开始进行基准测试 以前,先来花些时间回顾一下这些特性,这样有助于理解它们是如何工作的。

逸出分析 — 简析锁省略(Escape analysis - lock elision explained)

逸出分析是对运行中的应用程序中的全部引用的范围所做的分析。逸出分析是 HotSpot分析工作的一个组成部分。如果HotSpot(通过逸出分析)能够判断出指 向某个对象的多个引用被限制在局部空间内,并且所有这些引用都不能“逸出” 到这个空间以外的地方,那么HotSpot会要求JIT进行一系列的运行时优化。其中 一种优化就是锁省略(lock elision)。如果锁的引用限制在局部空间中,说明 只有创建这个锁的线程才会访问该锁。在这种条件下,同步块中的值永远不会存 在竞争。这意味这我们永远不可能真的需要这把锁,它可以被安全地忽略掉。考 虑下面的方法:

public String concatBuffer(String s1, String s2, String s3) {,
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
  }

图1. 使用局部的StringBuffer连接字符串

如果我们观察变量sb,很快就会发现它仅仅被限制在concatBuffer方法内部了 。进一步说,到sb的所有引用永远不会“逸出”到 concatBuffer方法之外,即声 明它的那个方法。因此其他线程无法访问当前线程的sb副本。根据我们刚介绍的 知识,我们知道用于保护sb的锁可以忽略掉。

从表面上看,锁省略似乎可以允许我们不必忍受同步带来的负担,就可以编写 线程安全的代码了,前提是在同步的确是多余的情况下。锁省略是否真的能发挥 作用呢?这是我们在后面的基准测试中将要回答的问题。

简析偏向锁(Biased locking explained)

大多数锁,在它们的生命周期中,从来不会被多于一个线程所访问。即使在极 少数情况下,多个线程真的共享数据了,锁也不会发生竞争。为了理解偏向锁的 优势,我们首先需要回顾一下如何获取锁(监视器)。

获取锁的过程分为两部分。首先,你需要获得一份契约.一旦你获得了这份契 约,就可以自由地拿到锁了。为了获得这份契约,线程必须执行一个代价昂贵的 原子指令。释放锁同时就要释放契约。根据我们的观察,我们似乎需要对一些锁 的访问进行优化,比如线程执行的同步块代码在一个循环体中。优化的方法之一 就是将锁粗化,以包含整个循环。这样,线程只访问一次锁,而不必每次进入循 环时都进行访问了。但是,这并非一个很好的解决方案,因为它可能会妨碍其他 线程合法的访问。还有一个更合理的方案,即将锁偏向给执行循环的线程。

将锁偏向于一个线程,意味着该线程不需要释放锁的契约。因此,随后获取锁 的时候可以不那么昂贵。如果另一个线程在尝试获取锁,那么循环线程只需要释 放契约就可以了。Java 6的HotSpot/JIT默认情况下实现了偏向锁的优化。

时间: 2024-08-30 14:17:21

Java 6中的线程优化真的有效么?的相关文章

Java 6中的线程优化真的有效么?——第二部分

在本文的第一部分中,我们通过一个单一线程的基准,比较了同步的 StringBuffer和非同步的StringBuilder之间的性能.从最初的基准测试结果来看 ,偏向锁提供了最佳的性能,比其他的优化方式更有效.测试的结果似乎表明获 取锁是一项昂贵的操作.但是在得出最终的结论之前,我决定先对结果进行检验 :我请我的同事们在他们的机器上运行了这个测试.尽管大多数结果都证实了我 的测试结果,但是有一些结果却完全不同.在本文的第二部分中,我们将更深入 地看一看用于检验测试结果的技术.最后我们将回答现实中

多线程问题-新手求助关于Java多线程中启动线程问题

问题描述 新手求助关于Java多线程中启动线程问题 public class Example15_1 { /** * @param args */public static void main(String[] args) { // TODO Auto-generated method stub SpeakHello speakHello; SpeakNinhao speakNinhao; speakHello = new SpeakHello(); speakNinhao= new Speak

Java基础-创建Java程序中的线程池

程序|创建 线程是Java的一大特性,它可以是给定的指令序列.给定的方法中定义的变量或者一些共享数据(类一级的变量).在Java中每个线程有自己的堆栈和程序计数器(PC),其中堆栈是用来跟踪线程的上下文(上下文是当线程执行到某处时,当前的局部变量的值),而程序计数器则用来跟踪当前线程正在执行的指令. 在通常情况下,一个线程不能访问另外一个线程的堆栈变量,而且这个线程必须处于如下状态之一: 1.排队状态(Ready),在用户创建了一个线程以后,这个线程不会立即运行.当线程中的方法start()被调

Java开发中的线程安全选择与Swing[Z]

安全 Swing API的设计目标是强大.灵活和易用.特别地,我们希望能让程序员们方便地建立新的Swing组件,不论是从头开始还是通过扩展我们所提供的一些组件. 出于这个目的,我们不要求Swing组件支持多线程访问.相反,我们向组件发送请求并在单一线程中执行请求. 本文讨论线程和Swing组件.目的不仅是为了帮助你以线程安全的方式使用Swing API,而且解释了我们为什么会选择现在这样的线程方案. 本文包括以下内容: 单线程规则:Swing线程在同一时刻仅能被一个线程所访问.一般来说,这个线程

Java开发中的线程安全选择与Swing

Swing API的设计目标是强大.灵活和易用.特别地,我们希望能让程序员们方便地建立新的Swing组件,不论是从头开始还是通过扩展我们所提供的一些组件. 出于这个目的,我们不要求Swing组件支持多线程访问.相反,我们向组件发送请求并在单一线程中执行请求. 本文讨论线程和Swing组件.目的不仅是为了帮助你以线程安全的方式使用Swing API,而且解释了我们为什么会选择现在这样的线程方案. 本文包括以下内容: ◆单线程规则:Swing线程在同一时刻仅能被一个线程所访问.一般来说,这个线程是事

详解Java编程中对线程的中断处理_java

1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时--都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制. 如果对Java中断没有一个全面的了解,可能会误以为被中断的线程将立马退出运行,但事实并非如此.中断机制是如何工作的?捕获或检测到中断后,是抛出InterruptedException还是重设中断状态以及在方法中吞掉中断状态会有什么后果?Thread.st

如何在Java编程中使用线程

Java平台从开始就被设计成为多线程环境.在你的主程序执行的时候,其它作业如碎片收集和事件处理则是在后台进行的.本质上,你可以认为这些作业是线程.它们正好是系统管理线程,但是无论如何,它们是线程.线程使你能够定义相互独立的作业,彼此之间互不干扰.系统将交换这些作业进或出CPU,这样(从外部看来)它们好象是同时运行的. 在你需要在你的程序中处理多个作业时,你也可以使用多个进程.这些进程可以是你自己创建的,你也可以操纵系统线程. 你进行这些多作业处理,要使用几个不同的类或接口: java.util.

简单介绍Java编程中的线程池_java

从 Java 5 开始,Java 提供了自己的线程池.线程池就是一个线程的容器,每次只执行额定数量的线程. java.util.concurrent.ThreadPoolExecutor 就是这样的线程池.它很灵活,但使用起来也比较复杂,本文就对其做一个介绍. 首先是构造函数.以最简单的构造函数为例: public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit uni

详解Java多线程编程中的线程同步方法_java

1.多线程的同步: 1.1.同步机制:在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子:成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们之间