Archive

Posts Tagged ‘concurrency’

2011, 掌握一门新语言:Erlang

April 17th, 2011 西坪 1 comment

我们已经进入了多核时代。 做了好些年并发编程设计,觉得虽然主流语言如Java、C/C++都不同程度地通过线程的方式对多核提供了支持,但是要真正理解并发,使用自如,绝非易事。对软件开发人员才是瓶颈的软件工业来说,改革底层的编程模型,以简便地支持多核程序设计,是未来的方向。

为并发而生的 Erlang 已经存活了很多年,只是一直不温不火,阳春白雪。而Java平台上的Scala,在这两年迅速进入软件工业界的视野。尽管其Actor模型借鉴自Eralng,运行在JVM之上,不像Erlang那样纯粹、简单, 但得力于Java平台庞大的现有代码、庞大的程序员阵营和各企业组织的大规模现有投资,很多人都认为Scala会更加成功。即使热爱 Erlang 的 Yariv Sadan — 他在 Facebook 用 Erlang 开发 Facebook Chat ,也在其争论 Erlang 与 Scala 的博文 《Erlang vs. Scala》 的结尾写道:“ Regardless of these differences, though, I think that Scala has a good chance of being the more popular language of the two.” 这篇文章很精彩,后面的评论也很精彩,值得一读。 有趣的是,作者说有些人认为 Erlang来自外国 (作者应该是美国人,而Erlang来自瑞典) 是其未能成为主流语言的一个原因。(A few times, I’ve seen the following take on the relative mertis of Scala and Erlang: Erlang is great for concurrent programming and it has a great track record in its niche, but it’s unlikely to become mainstream because it’s foreign and it doesn’t have as many libraries as Java.)

去年跟风了解了 Golang,写过几个小程序。Golang的方式略有有些易于C/Java,但总的来说,还是非常像的,上手不难。尤其是喜欢C的人,要选择一门多核时代的语言去学习,Golang 是最合适的了。不过,我还没有弄透Golang对并发的支持为什么优于传统语言,是的,它的并发基于消息传递的,但到底是怎么实现的?又为何如此实现呢?

Erlang 给我的感受则完全不同,变量竟然是immutable的,= 竟然不是赋值符号,字符串是整形值,一个类型也没有 :) , 刘姥姥进了大观园,完全是一个惊奇的世界。不过除了惊讶,我便很快喜欢它immutable的变量,也对久仰大名的 List Comprehension 感到异常惊喜。List Comprehension 跟 Map/Reduce的区别还是很明显的。 Erlang 真的是太惊艳了,学习它是必须的。毋庸置疑,它肯定会促使我改变思考事情的方式。

对于Scala,扫描了下它的文档。不过我更关心的是 ActorCSP 。Scala 与 Erlang 的并发都源自 Actor模型,有Erlang就可以了解了。对于一个常跟Java打交道的程序员,Scala需要用了将来再现学就行。

我想要理解的是Erlang/Go与Java/C不同的并发模型,它们的内存模型有哪些异同。也许你会说减少了上下文切换,减少了CPU Cache丢失,减少系统内存开销,减少抢断带来的损失等等,但我想探寻的是它们具体是怎么做的呢? 多核的变革已经来临,或许改进应该从计算机体系的顺序模型开始,硬件结构与软件模型一起改进(看看 Comuunications Of ACM《Memory Models: A Case for Rethinking Parallel Languages and Hardware》)。 何况除了并发,Erlang 还有更多值得发掘的东西。

下面这是相关的几篇值得一读的好文:

———————————–
参考:

Categories: $Programming Tags: , , ,

Proxool导致的内存泄漏

April 1st, 2011 西坪 No comments

Proxool是一个很优秀的开源连接池。我曾经比较过dbcp, c3p0proxool 这三个连接池,阅读和分析它们的代码。相比之下,proxool 用了cglib,其源码显得相当简洁优雅。 但是很不幸,在这一次,内存泄漏的帐要算在它头上。 我们使用的是proxool 的0.9.1的版本,环境是Oracle JDBC5 + IBM JDK5。

0.9.1版本是proxool 的最新版本。我们有一个应用据称运行以来,就一直处于内存溢出的阴霾之中。追赶数据时的某些巨忙的系统可能会一天内死好些次,甚至达十几次。不得不采用一些监控脚本定时重启。去年12月我接手以来,也被这个问题困扰,最糟糕的时候甚至某些硬件设施很好的部署实例的多达50+GB的堆也能在一天之内吃光。

出错的原因很简单,proxool使用了cglib,它用WrappedConnection代理实际的Conneciton。在运行WrappedConnection的方法时,包括其finalize方法,都会调用Conneciton.isClosed()方法去判断是否真的需要执行某些操作。不幸的是Oracle JDBC中的这个方法是同步的,锁是连接对象本身。于是, Finalizer线程回收刚执行过的WrappedConnection对象时就总会与还在使用Connection的各个工作线程争用锁。

贴一张简单的图,你就懂了:
thread_blocked_2

解决方案是在WrappedConnection.java的第114行,做一点小小的改动,在调用的方法为finalize方法时,不要去掉isClosed()方法。事实上,WrappedConnection#finalize()方法的调用与Connection根本没啥关系。Oracle JDBC本身没有实现finalize()方法,是cglib代理的对象自己生成了一个finalize()方法而已。
修改前的114行:

if (proxyConnection != null && proxyConnection.isReallyClosed()) {

修改后的114行:

if (proxyConnection != null && !concreteMethod.getName().equals(FINALIZE_METHOD) && proxyConnection.isReallyClosed()) {

点此查看sourceforge上的完整的WrappedConnection.java

效果是很明显的。一方面,内存消耗明显降低。之前,所有的部署实例都会隔段时间就当掉,某些部署实例的52G的堆都会溢出!现在,系统再忙其堆大小也基本上在原来那个设得老大的Xms参数之下。 第二方面,吞吐量忽然提高了许多,没有烦人的回收线程(其优先级要高点)的干扰,当然应该要跑得更快些。

感兴趣的朋友可以下载我这里提供的jar包(-target 1.5),(源码是直接从官网下的proxool-0.9.1-source.zip)。这个jar包也解决了ProxoolDataSource在Spring中作为bean配置时有三个属性(maximumConnectionLifetime,houseKeepingSleepTime,overloadWithoutRefusalLifetime)不能注入的问题。详细请看这篇博文

下载 proxool-0.9.1

也可以应用patch自己编译一个。patch我上传到Bug页面了,请去那下载。

J2EE/Oracle性能调优实录

February 18th, 2011 西坪 No comments

这两天,有一个新接手的项目性能表现较差,不能满足要求。这个项目在这之前已经写完很久了,但是一些关键部署点一直没有跟上进度。此系统是用Java编写的多线程程序的,主要是从Oracle中读取数据,然后根据业务进行统计运行,在将计算结果放回Oracle中。

这个系统的性能的直观指标,就是每天能处理多少数据量。这个指标很不理想,速度很慢,拖后与现场数据生成的速度。调优的标准,就是每日处理量。

第一次优化: 提高JDBC的fetchSize

首先,分析系统各个环节的时间消耗,弄清瓶颈在哪里。

在进行了一些综合分析后,得出主要时间耗费在数据库的查询上。而从v$session_wait中查询,发现有很多 SQL*Net message from client 事件的等待,时间很长,有数百秒之多。 显然是客户端慢了。但是因为应用端是有连接池的,是不是空闲连接导致的呢? 有一些怀疑。

接下来,在数据库上用 sqlplus 执行了相同的SQL,测量其时间其实是很快的,远远小于应用消耗在数据获取的时间。联系其上面提到的等待事件,事情变逐渐明朗: 网络传输慢了。

于是检查了代码,发现大批量的数据查询竟然没有提升fetchSize属性。在调整该属性为500以后,很快收到了正面反馈。处理速度提升了将近一倍。

回去在检查Oracle中的等待事件,发现已经降到200名以外了(是与其他应用共用数据库的)。

有关 sqlnet message from client 事件的分析,可以参考 Asktom 上的文章 AskTom上的另一篇文章,则描述了类似于连接池等待,或者是在sqlplus中打开会话不操作也会导致该事件。所以在见到这个事件时还是需要仔细甄别一下的。

结论: 适当调整 FetchSize是很有必要的。Oracle JDBC默认的fetchSize为10。FetchSize的提升,一方面是减少了客户端与服务器之间的来回交互次数;另一方面,也会有效减少数据库服务器逻辑读的数量(记得是《Troubleshooting Oracle Performance》中有讲到)。

修改完成后,除了观测到 sqlnet message from client 事件消失外,发现 sys cpu 也有小幅增加。

第二次优化:减小锁定(lock)范围

但是,接下来的观测,证明这个速度还是不合意,达不到要求。于是继续分析。

仍然是分析其主要的时间消耗在什么地方。这次发现时间主要是消耗在程序对数据的处理过程中。数据的处理大部分都是在Java进程中的运算,处理过程中还可能调用到Memcached。是不是网络速度比较慢呢?尝试测试了一下网速,千兆网络的吞吐率在应用并发时还能挤出三分之一左右用来做测试文件的传输,用ping发现其latency也不算高。

在此前的观测中,发现应用服务器上的 user cpu 比较好,在30%~70%之间,主要是被JVM进程消耗了,系统负载在5.0左右。觉得有些奇怪的,因为一般这样的应用瓶颈都出在IO上。于是做了一次代码复查,很快发现有一个在方法签名上的synchronize 关键字。这个类是一个服务bean, 在spring中管理的。这时问题变得清晰起来,所有的处理任务线程都要在这里排队。这样正好符合了此前观测到的cpu现象。

改掉这个关键字,缩小锁定范围,速度又上升了将近一倍。user cpu很快降下来,系统负载也下来了,稍稍大于1。

此时,速度虽能勉强应付,但还是不够快。此时更多的时间消耗在数据的写回上面。处理速度提升了,写回阶段的压力便也迅速提升,每单位数据的写回时间有20%左右的上浮。于是,需要第三次优化,在数据写回方面提升速度。

第三次优化

(Update Apr 6th):
没心思再仔细写过程了,简略如下:
1) Oracle是多个系统共享的,数据写入成为瓶颈。在内存问题解决后,在本地内存中缓存结果,最后再集中提交,减少更新次数。数度大增2.5倍左右。
2) 将从数据库中的查询和在内存中的处理划分为两个线程并发处理,速度提高50-70%。
3) 然后,Memcached集群成为瓶颈,内网延迟成为问题。但此时速度已经足够快了,因此暂时搁置下一步优化。如果要继续提速,则可能在本地建立一个Memcached实例,或者在程序中使用一个足够大的LRU缓存以减少与Memcached交互的次数。

至此,数据后台处理速度完全达到并超过预期。

Categories: $Performance Tags: , , , , ,

Java中的Volatile关键字

December 13th, 2010 西坪 2 comments

前两天,并行实验室发表了一篇有关volatile关键字的文章[1],而该文参考文献中的Sayonara volatile[2]则更直接地要与volatile再见。两篇文章不谋而合,从c/c++到Java/.net,将这几门语言中的volatile过了一遍。列举出各种乱象,看上去volatile是如此不堪。

确实,volatile的语义不是那么常见。不是锁,但是它又拥有锁的部分特性(可见性,Java 5以后还有顺序保证)。如果不能正确理解,确实是个容易出错的地方。但是如果理解了用对了,仅就Java 5以及以后的版本而言,在目前阶段,volatile对性能上的提升还是有帮助的。

Volatile在Java中的语义以及历史

维基百科上,对Java中的Volatile给了一个准确的定义[6]

  • (In all versions of Java) There is a global ordering on the reads and writes to a volatile variable. This implies that every thread accessing a volatile field will read its current value before continuing, instead of (potentially) using a cached value. (However, there is no guarantee about the relative ordering of volatile reads and writes with regular reads and writes, meaning that it’s generally not a useful threading construct.)
  • (In Java 5 or later) Volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.

翻译过来就是:

  • (在所有的Java版本中)对volatile变量上的读和写有一个全局排序。这暗示着每个线程访问一个volatile域时在往下运行之前将读到当前的值,而不是(有可能)使用一个缓存值。(然而,在volatie变量的读写与常规的读写之间的排序没有保证,意味着volatile通常不是一个有用的线程化的构造)
  • (在Java5及之后版本中) Volatile读和写建立了一种happens-before关系,就像获取和释放一个互斥体。

在Java中,要正确地并发,必须在原子性、可见性和顺序性三个方面做出保证。在Java 5以前的版本中,volatile就已经实现了可见性,但是因为不能保证与普通变量读写之间的顺序,故而经常是没有用的。在Java 5以后的版本中,则额外保证了其与普通变量读写的顺序性。这里不是特别好懂,我们将javamx上的例子[3] [4]改造一下:

public class MyBrokenFactory {
  private static volatile MyFactory instance;
  private int field1, field2 ...
 
  public static MyBrokenFactory getFactory() {
    // This is incorrect: don't do it at home, kids!
    if (instance == null) {
      synchronized (MyBrokenFactory.class) {
        if (instance == null)
          instance = new MyBrokenFactory();
      }
    }
    return instance;
  }
 
  private MyBrokenFactory() {
    field1 = ...
    field2 = ...
  }
}

上面的代码,在Java5以前,这个例子是不能正确工作的,因为volatile不保证对field1,field2的初始化(常规读写)一定在对volatile变量instance的写之前完成。但双检查锁本身并非volatile导致的,反而是Java 1.5的volatile让双检查锁能正确工作了。并行实验室将双检查锁不正确算在volatile的头上,有点冤了。

正是因为volatile在Java 5以前没有得到正确实现,更添加了不少复杂性。在Java 5之前,volatile基本没用处。

Volatile的实现机制分析

在Hotspot JVM中,
a) 在JVM层次,对volatile变量在线程本地工作区中不做缓存,对volatile的读写总是指向堆中的引用。可以视作在一个assign指令后总是跟着一个store指令[5]
b) 在机器码执行层次,通过内存屏障指令等迫使CPU不重排序,清除缓存,详细请参考《Memory Barriers and JVM Concurrency》的分析。

这与synchronized是有明显不同的,synchronized通常需要原子锁定,在SMP上要通过锁定总线等方式来实现,其代价在大多数平台上通常要比volatile高得多。

Performance

图一运行100万次
引入Volatile的主要目的,就是为了性能。我分下面几个方面来谈谈volatile在目前的Java平台中的作用。

直接性能比较

简单写了一个测试来比较一下volatile以及它的两种替代品 — 原子类型(atomic)和锁(此处指synchronized)。例子的代码改编自庄周梦蝶slides《Java NIO trick and trap》中的SystemTimer(部分代码省略,完整源码在Github上)。这个测试只是一个例子,但其实在网络库里都要做定时,原理也大抵如此。

第一个版本如下:

package me.xiping.volitileverify;
public class SystemTimerV1 {
	private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
	private static final long tickUnit = Long.parseLong(System.getProperty(
			"notify.systimer.tick", "50"));
	private static volatile long time = System.currentTimeMillis();
 
	private static class TimerTicker implements Runnable {
		public void run() {
			time = System.currentTimeMillis();
		}
	}
 
	public static long currentTimeMillis() {
		return time;
	}
 
	static {
		executor.scheduleAtFixedRate(new TimerTicker(), tickUnit, tickUnit,
				TimeUnit.MILLISECONDS);
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				executor.shutdown();
			}
		});
	}
}

代码的重点在于volatile修饰的time域。time是一个只有一个线程写,其他线程都是读的域,没有并发修改,这里要解决的就是可见性。这正是volatile最适合的场合。

第二个版本使用synchronize关键字,与版本V1的不同如下:

package me.xiping.volitileverify;
public class SystemTimerV2 {
	....
	private static long time = System.currentTimeMillis();
 
	private static class TimerTicker implements Runnable {
		public void run() {
			synchronized (SystemTimerV2.class) {
				time = System.currentTimeMillis();
			}
		}
	}
 
	public static synchronized long currentTimeMillis() {
		return time;
	}
	....
}

第二个版本使用Java的内部锁来同步,既保证了原子性,也保证了可见性。第三个版本则是使用AtomicLong来实现的,如下:

package me.xiping.volitileverify;
 
public class SystemTimerV3 {
	......
	private static AtomicLong time = new AtomicLong(System.currentTimeMillis());
 
	private static class TimerTicker implements Runnable {
		public void run() {
			time.set(System.currentTimeMillis());
		}
	}
 
	public static long currentTimeMillis() {
		return time.get();
	}
	.....
}

在我的本上(cpu: 2core; OS: win7/cygwin; java:1.6.0_13)运行100万次,其性能如上面右图一所示(单位:ms)。
图二运行1千万次
运行1000万次性能如图二所示(注意纵坐标的值与图一不一样)。

结论:性能上的优越性是显而易见的。volatile比AtomicLong明细要快,相比于synchronized则有几倍几十倍的提高了。
(欢迎在不同平台下测试并在评论中给一个反馈,反馈时请包括处理器信息(平台,core数),OS和Java版本。源代码在这里。编译和运行需要Ant,具体指南请参考这里。)

[Update, Dec15th: 图片是用Google Chart API 生成的,在源代码中包含着两个自动执行测试并生成图片URL的脚本。脚本需运行在Bash环境下,Windows上需在Cygwin下运行。]

Mina/netty对volatile的利用

写博文时顺便统计了下volatile在一些性能比较重要的程序中的情况,我顺手统计了手边有的mina和netty两个项目的核心代码,数据如下:

Project sources path synchronized occurrence volatile occurrence
netty src/main/java 150 177
mina src/main/java 216 55

其中,mina是2009-3-31日Subversion的trunk,如下:

$svn info
Path: .
URL: http://svn.apache.org/repos/asf/mina/trunk
Repository Root: http://svn.apache.org/repos/asf
Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68
Revision: 761900
Node Kind: directory
Schedule: normal
Last Changed Author: jvermillard
Last Changed Rev: 760493
Last Changed Date: 2009-03-31 23:49:15 +0800 (Tue, 31 Mar 2009)

netty的版本信息是:

$git log
commit 1ffb1aea75c36def56b709bd0892b19df78d9249
Author: Trustin Lee <trustin@gmail.com>
Date:   Fri Nov 12 10:20:03 2010 +0900

从侧面证明了volatile其实是很重要的,在性能很重要的场合,应用还是比较多的。

结论

总而言之,相比与Atomic和synchronized,volatile在性能上还是有显著的优势。

不过,正如文章中开头讨论的那样,volatile的缺点也显而易见,需要开发者对volatile的应用需要对内存模型的理解,否则容易误解而造成错误。在并发程序中,其仅可用于JVM支持的几个基本类型(int,short,long,double,reference, etc.),而这几个基本类型皆有对应的完整并发特性的包装原子类型(java.util.concurrent.atomic.*),虽然其性能与volatile有些差距,但大部分情形下也可代替volatile(但用synchronized来代替volatile是很不划算的)。

  1. 为什么在多核多线程程序中要慎用volatile关键字?
  2. Sayonara volatile
  3. Double-checked Locking (DCL) and how to fix it
  4. Double-checked Locking (DCL) and how to fix it (ctd)
  5. JVM specification#Threads and Locks
  6. Volatile variable#Java
  7. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
  8. Memory Barriers and JVM Concurrency
Categories: $Performance Tags: , ,

文件锁与进程互斥

January 8th, 2010 西坪 3 comments

有的情况下需要在进程之间实现互斥,这对于普通Java程序来说,因为是运行在虚拟机里的原因,手段远不如原生程序方便,但是也有一些简单的方法可以做到。第一个就是使用Socket监听某个端口,第二种方法是使用文件锁定。

使用Socket实现进程互斥

因为Java服务器套接字都是基于系统的socket接口实现的,而socket接口会保证一个套接字端点在整个系统中是唯一的,所以只需要在应用启动时监听一个端口,就可以让系统不同时启动。虽然方法比较土,但是也能用。

try {
	ServerSocket ss = new ServerSocket(3030);
}catch (Exception e) {
	logger.error("Cannot start", e.printStackTrace());
	System.exit();
}

使用文件锁在进程间同步

另一个实现Java进程间互斥的方法,就是文件锁。不过与上面使用套接字不同,使用文件锁还可以实现更高级的互斥/协作。完全可以用文件锁来实现不同Java进程之间的同步,基本思路与在线程之间使用读写锁同步相似。

JDK本身直接支持文件锁,在FileChannel下有几个与文件锁有关的方法,可以打开一个FileLock对象。当调用FileLock#release()方法,或者Channel关闭,或者JVM退出时都会释放该锁。文件锁是JVM全局的,与Java内部的锁/线程等无关,是进程之间的竞争关系。如果你不想发生意外你的程序挂起,那就应该用FileChannel#trylock,而不是直接使用FileChannel#lock(),这样在不能加锁时可以休眠一段时间再等等,或者超时退出。

文件锁像读写锁一样,可以是加共享锁,也可以加独占锁,还可以给锁定限制范围。这是操作系统本身提供的功能,JDK在操作系统层面上做了封装。在Linux下,其实就是调用fcntl来设置相关的状态。如下面的代码,会给文件加独占锁,如果再起新的进程,进程就会打开文件失败退出。

     char* lockfile;
      int lockfd;
      struct flock lock;
      time_t t;   
 
      if ((lockfd = open(lockfile, O_RDWR)) == -1) {
          fprintf(stdout, "open file %s fail: %s \n", lockfile, strerror(errno));
          exit(1);
      }
 
      t = time(NULL);
 
      lock.l_type = F_WRLCK;
      lock.l_start = 0;
      lock.l_len = 0;
      lock.l_whence = SEEK_SET;
      if (fcntl(lockfd, F_SETLKW, &lock) == -1) {
          fprintf(stdout, "lock file %s fail: %s \n", lockfile, strerror(errno));
      }
      fprintf(stdout, "lock file %s success(%d): %ld \n", lockfile, (int)getpid(), time(NULL)-t);
      sleep(30);
Categories: $Programming Tags: , , ,

细节背后:为什么线程协作之前必须先获得锁?

October 3rd, 2009 西坪 No comments

为什么Object.wait()/notify()/notifyAll() 之前必须获得锁? 这是JLS的规定。Wait-notify机制是围绕监控器锁进行的,获得锁是很自然的前提,自身没有拿到锁之前,怎么能够尝试去操作靠锁来调控的线程呢?不过今天偶尔有时间,就看下Sun Hotspot是怎么实现这一机制的。

当我们执行下面的代码时,线程会抛出异常java.lang.IllegalMonitorStateException: current thread not owner。

public class WaitNotifyCompilerCode {
	private String aString = "Hello World!";
 
	public static void main(String[] args) {
		System.out.println("Execute start ....");
		final WaitNotifyCompilerCode w = new WaitNotifyCompilerCode();
		w.wait1SecAndPrintString();
		System.out.println("Execute end ....");
	}
 
	public void wait1SecAndPrintString() {
		try {
			this.wait(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(aString);
	}
 
}

异常栈的信息如下:

Exception in thread "main" java.lang.IllegalMonitorStateException: current thread not owner
	at java.lang.Object.wait(Native Method)
	at com.feihoo.test.waitnotify.WaitNotifyCompilerCode.wait1SecAndPrintString(WaitNotifyCompilerCode.java:35)
	at com.feihoo.test.waitnotify.WaitNotifyCompilerCode.main(WaitNotifyCompilerCode.java:27)

深入查看 OpenJDK的源码,找到 Object.wait() 函数本地代码:

JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
  JVMWrapper("JVM_MonitorWait");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  assert(obj->is_instance() || obj->is_array(), "JVM_MonitorWait must apply to an object");
  JavaThreadInObjectWaitState jtiows(thread, ms != 0);
  if (JvmtiExport::should_post_monitor_wait()) {
    JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
  }
  ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END

重点看倒数第二行,调用的函数如下面的代码:

// NOTE: must use heavy weight monitor to handle wait()
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  if (UseBiasedLocking) {
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  if (millis < 0) {
    TEVENT (wait - throw IAX) ;
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  monitor->wait(millis, true, THREAD);

而上面的代码中最后一行的函数里,在做实际操作之前调用了下面的宏:

// A macro is used below because there may already be a pending
// exception which should not abort the execution of the routines
// which use this (which is why we don't put this into check_slow and
// call it with a CHECK argument).
 
#define CHECK_OWNER()                                                             \
  do {                                                                            \
    if (THREAD != _owner) {                                                       \
      if (THREAD->is_lock_owned((address) _owner)) {                              \
        _owner = THREAD ;  /* Convert from basiclock addr to Thread addr */       \
        _recursions = 0;                                                          \
        OwnerIsThread = 1 ;                                                       \
      } else {                                                                    \
        TEVENT (Throw IMSX) ;                                                     \
        THROW(vmSymbols::java_lang_IllegalMonitorStateException());               \
      }                                                                           \
    }                                                                             \
  } while (false)
Categories: $Programming Tags: , ,