在Java中使用协程(Coroutine)

本篇blog将讲述coroutine的一些背景知识,以及在Java中如何使用Coroutine,包括一个简单的benchmark对比,希望能借助这篇blog让大家了解到更多在java中使用coroutine的方法,本篇blog的PDF版本可从此下载:http://www.bluedavy.com/open/UseCoroutineInJava.pdf
在讲到具体内容之前,不能不先讲下
Coroutine的一些背景知识,来先具体了解下什么是Coroutine

1.       背景知识

         现在的操作系统都是支持多任务的,多任务可通过多进程或多线程的方式去实现,进程和线程的对比就不在这里说了,在多任务的调度上操作系统采取抢占式和协作式两种方式,抢占式是指操作系统给每个任务一定的执行时间片,在到达这个时间片后如任务仍然未释放对CPU的占用,那么操作系统将强制释放,这是目前多数操作系统采取的方式;协作式是指操作系统按照任务的顺序来分配CPU,每个任务执行过程中除非其主动释放,否则将一直占据CPU,这种方式非常值得注意的是一旦有任务占据CPU不放,会导致其他任务饿死的现象,因此操作系统确实不太适合采用这种方式。

         说完操作系统多任务的调度方式后,来看看通常程序是如何实现支持高并发的,一种就是典型的基于操作系统提供的多进程或多线程机制,每个任务占据一个进程或一个线程,当任务中有IO等待等动作时,则将进程或线程放入待调度队列中,这种方式是目前大多数程序采取的方式,这种方式的坏处在于如想支持高的并发量,就不得不创建很多的进程或线程,而进程和线程都是要消耗不少系统资源的,另外一方面,进程或线程创建太多后,操作系统需要花费很多的时间在进程或线程的切换上,切换动作需要做状态保持和恢复,这也会消耗掉很多的系统资源;另外一种方式则是每个任务不完全占据一个进程或线程,当任务执行过程中需要进行IO等待等动作时,任务则将其所占据的进程或线程释放,以便其他任务使用这个进程或线程,这种方式的好处在于可以减少所需要的原生的进程或线程数,并且由于操作系统不需要做进程或线程的切换,而是自行来实现任务的切换,其成本会较操作系统切换低,这种方式也就是本文的重点,Coroutine方式,又称协程方式,这种方式在目前的大多数语言中都有支持。

         各种语言在实现Coroutine方式的支持时,多数都采用了Actor Model来实现,Actor Model简单来说就是每个任务就是一个ActorActor之间通过消息传递的方式来进行交互,而不采用共享的方式,Actor可以看做是一个轻量级的进程或线程,通常在一台4G内存的机器上,创建几十万个Actor是毫无问题的,Actor支持Continuations,即对于如下代码:

         Actor

                   act方法

                            进行一些处理

创建并执行另外一个Actor

                            通过消息box阻塞获取另一个Actor执行的结果

                            继续基于这个结果进行一些处理

         在支持Continuations的情况下,可以做到消息box阻塞时并不是进程或线程级的阻塞,而只是Actor本身的阻塞,并且在阻塞时可将所占据的进程或线程释放给其他Actor使用,Actor Model实现最典型的就是erLang了。

         对于Java应用而言,传统方式下为了支持高并发,由于一个线程只能用于处理一个请求,即使是线程中其实有很多IO中断、锁等待也同样如此,因此通常的做法是通过启动很多的线程来支撑高并发,但当线程过多时,就造成了CPU需要消耗不少的时间在线程的切换上,从而出现瓶颈,按照上面对Coroutine的描述,Coroutine的方式理论上而言能够大幅度的提升Java应用所能支撑的并发量。

2.       Java中使用Coroutine

         Java尚不能从语言层次上支持Coroutine,也许Java 7能够支持,目前已经有了一个测试性质的版本[1],在Sun JDK 7尚未正式发布的情况下如希望在Java中使用CoroutineScalaKilim是可以做的选择,来分别看下。

         Scala是现在很火的语言之一,Twitter消息中间件基于Scala编写更是让Scala名声鹊起,除了在语法方面所做出的改进外,其中一个最突出的特色就是Scala ActorScala ActorScala用于实现Coroutine的方式,先来具体看看ScalaCoroutine支持实现的关键概念。

l  Actor

Scala Actor可以看做是一个轻量级的Java Thread,其使用方式和Java Thread基本也一致,继承Actor,实现act方法,启动时也是调用start方法,但和Java Thread不同的是,Scala Actor可等待外部发送过来的消息,并进行相应的处理。

l  Actor的消息发送机制

发送消息到Actor的方式有异步、Future两种方式,异步即指发送后立即返回,继续后续流程,使用异步发送的方法为:actor ! MessageObject,其中消息对象可以为任何类型,并且Scala还支持一种称为case Object的对象,便于在收到消息时做pattern matching

Future方式是指阻塞线程等待消息处理的结果,使用Future方式发送的方法为:actor !! MessageObject,在等待结果方面,Scala支持不限时等待,限时等待以及等待多个Future或个别Future完成,使用方法如下:

val ft=actor !! MessageObject // Future方式发送消息

val result=ft() // 不限时等待

val results=awaitAll(500,ft1,ft2,ft3)  // 限时等待多个Future返回值

val results=awaitEither(ft1,ft2) // 等待个别future完成

接收消息方通过reply方法返回Future方式所等待的结果。

l  Actor的消息接收机制

当代码处于Actoract方法或Actor环境(例如为Actoract方法调用过来的代码)中时,可通过以下两种方式来接收外部发送给Actor的消息:一为receive方式,二为react方式,代码例子如下:

receive{

         case MessageObject(args) => doHandle(args)

}

react{

         case MessageObject(args) => doHandle(args)

}

receivereact的差别在于receive需要阻塞当前Java线程,react则仅为阻塞当前Actor,但并不会阻塞Java线程,因此react模式更适合于充分发挥coroutine带来的原生线程数减少的好处,但react模式有个缺点是react不支持返回。

receivereact都有限时接收的方式,方法为:receiveWithin(timeout)reactWithin(timeout),超时的消息通过case TIMEOUT的方式来接收。

下面来看基于Scala Actor实现并发处理请求的一个简单例子。

         class Processor extends Actor{

                   def act(){

                            loop{

                                     react{

                                               case command:String => doHandle(command)

}

}

                   }

 

                   def doHandle(command:String){

                            // 业务逻辑处理

}

}

当需要并发执行此Processor时,在处理时需要的仅为调用以下代码:

val processor=new Processor()

processor.start

processor ! “Hello”

         从以上说明来看,要在旧的应用中使用Scala还是会有一些成本,部署运行则非常简单,在Scala IDE Plugin编写了上面的scala代码后,即生成了java class文件,可直接在jvm中运行。

Kilim是由剑桥的两位博士开发的一个用于在Java中使用Coroutine的框架,Kilim基于Java语法,先来看看Kilim中的关键概念。

l  Task

可以认为Task就是Actor,使用方式和Java Thread基本相同,只是继承的为Task,覆盖的为execute方法,启动也是调用taskstart方法。

l  Task的消息发送机制

Kilim中通过Mailbox对象来发送消息,Mailbox的基本原则为可以有多个消息发送者,但只能有一个消息接收者,发送的方式有同步发送、异步发送和阻塞线程方式的同步发送三种,同步发送是指保证一定能将消息放入发送队列中,如当前发送队列已满,则等待到可用为止,阻塞的为当前Task;异步发送则是尝试将消息放入发送队列一次,如失败,则返回false,成功则返回true,不会阻塞Task;阻塞线程方式的同步发送是指阻塞当前线程,并保证将消息发送给接收者,三种方式的使用方法如下:

mailbox.put(messageObject); // 同步发送

mailbox.putnb(messageObject); // 异步发送

mailbox.putb(messageObject); // 阻塞线程方式发送

l  Task的消息接收机制

Kilim中通过Mailbox来接收消息,接收消息的方式有同步接收、异步接收以及阻塞线程方式的同步接收三种,同步接收是指阻塞当前Task,直到接收到消息才返回;异步接收是指立刻返回Mailbox中的消息,有就返回,没有则返回null;阻塞线程方式的同步接收是指阻塞当前线程,直到接收到消息才返回,使用方法如下:

mailbox.get(); // 同步接收,传入long参数表示等待的超时时间,单位为毫秒

mailbox.getnb(); // 异步接收,立刻返回

mailbox.getb(); // 阻塞线程方式接收

下面来看基于Kilim实现并发处理请求的一个简单例子。

         public class Processor extends Task{

                   private String command;

                   public Processor(String command){

                            this.command=command;

}

public void execute() throws Pausable,Exception{

         // 业务逻辑处理

}

}

在处理时,仅需调用以下代码:

Task processor=new Processor(command);

processor.start();

从以上代码来看,Kilim对于Java人员而言学习门槛更低,但对于需要采用coroutine方式执行的代码在编译完毕后,还需要采用Kilimkilim.tools.Weaver类来对这些已编译出来的class文件做织入,运行时需要用织入后生成的class文件才行,织入的方法为:java kilim.tools.Weaver –d [织入后生成的class文件存放的目录] [需要织入的类文件所在的目录],目前尚没有Kilim IDE Plugin可用,因此weaver这个过程还是比较的麻烦。

上面对ScalaKilim做了一个简单的介绍,在实际Java应用中使用Coroutine时,通常会出现以下几种典型的更复杂的使用场景,由于Actor模式本身就是异步的,因此其天然对异步场景支持的就非常好,更多的问题会出现在以下几个同步场景上,分别来看看基于ScalaKilim如何来实现。

l  Actor同步调用

Actor同步调用是经常会出现的使用场景,主要为Actor发送消息给其他的Actor处理,并等待结果才能继续。

n  Scala

对于这种情况,在Scala 2.7.7中,目前可采取的为以下两种方法:

一种为通过Future方式发送消息来实现:

class Processor(command:String) extends Actor{

         def act(){

                   val actor=new NetSenderActor()

                   val ft=actor !! command

                   println(ft())

}

}

class NetSenderActor extends Actor{

         def act(){

                   case command:String => {

                            reply(“received command:”+command)

}

}

}

第二种为通过receive的方式来实现:

class Processor(command:String) extends Actor{

         def act(){

                   val actor=new NetSenderActor()

                   actor ! command

                   var senderResult=””

receive{

                            case result:String => {

senderResult=result

}

}

println(senderResult)

}

}

class NetSenderActor extends Actor{

         def act(){

                   case command:String => {

                            sender ! (“received command:”+command)

}

}

}

但这两种方式其实都不好,因为这两种方式都会造成当前Actor的线程阻塞,这也是因为目前Scala版本对continuations尚不支持的原因,Scala 2.8版本将提供continuations的支持,希望到时能有不需要阻塞Actor线程实现上述需求的方法。

还有一种常见的场景是Actor调一段普通的Scala类,然后那个类中进行了一些处理,并调用了其他Actor,此时在该类中如需要等待Actor的返回结果,也可使用上面两种方法。

n  Kilim

Kilim中要实现Task之间的同步调用非常简单,代码如下:

public class TaskA extends Task{

         public void execute() throws Pausable,Exception{

                   Mailbox<Object> result=new Mailbox<Object>();

Task task=new TaskB(result);

                   task.start();

                   Object resultObject=result.get();

                   System.out.println(resultObject);

}

}

public class TaskB extends Task{

         private Mailbox<Object> result;

public TaskB(Mailbox<Object> result){

                   this.result=result;

}

public void execute() throws Pausable,Exception{

         result.put(“result from TaskB”);

}

}

KilimMailbox.get并不会阻塞线程,因此这种方式是完全满足需求的。

l  普通Java代码同步调用Actor

由于已有的应用是普通的Java代码,经常会出现这样的场景,就是希望实现在这些Java代码中同步的调用Actor,并等待Actor的返回结果,但由于ScalaKilim都强调首先必须是在ActorTask的环境下才行,因此此场景更佳的方式应为Scala Actor(Kilim Task) à Java Code à Scala Actor(Kilim Task),这种场景在对已有的应用中会是最常出现的,来看看在ScalaKilim中如何应对这样的需求。

n  Scala

目前Scala中如希望在Java Code中调用Scala Actor,并等待其返回结果,暂时还没办法,做法只能改为从Java Code中去调一个ScalaObject,然后在这个Object中调用Actor,并借助上面提到的receivefuture的方法来获取返回值,最后将这个返回值返回Java Code

n  Kilim

目前Kilim中如希望实现上面的需求,其实非常简单,只需要在Java Code的方法上加上Throw Pausable,然后通过mailbox.get来等待Kilim Task返回的结果即可,在Kilim中只要调用栈上的每个方法都有Throw Pausable,就可在这些方法上做等待返回这类的同步操作。

从上面这两个最常见的需求来看,无疑Kilim更符合需求,但要注意的是对于Kilim而言,如果出现Task à nonpausable method à pausable method这样的状况时,pausable method中如果想执行阻塞当前Task的操作,是无法做到的,只能改造成Task (mailbox上做等待,并传递mailbox给后续步骤) à nonpausable method (传递mailbox) à pausable method (将逻辑转为放入一个Task中,并将返回值放入传递过来的mailbox),这种状况在面对spring aop、反射调用等现象时就会出现了,目前kilim 0.6的版本尚未提供更透明的使用方法,不过据kilim作者提供的一个试用版本,其中已经有了对于反射调用的透明化的支持,暂时在目前只能采用上述方法,迁移成本相对较大,也许以后的kilim版本会考虑这样的场景,提供相应的方法来降低迁移的成本。

3.       性能、所能支撑的并发量对比

在对ScalaKilim有了这些了解后,来具体看看采用ScalaKilim后与传统Java方式在性能、所能支撑的并发量上的对比。

l  测试模型

采用一个比较简单的模型进行测试,具体为有4个线程,这4个线程分别接收到了一定数量的请求,每个请求需要交给另外一个线程去执行,这个线程所做的动作为循环10次获取另外一个线程的执行结果,此执行线程所做的动作为循环1000次拼接一个字符串,然后返回。

l  实现代码

由于目前Scala版本对Continuation支持不够好,但上面的场景中又有此类需求,所以导致Scala版本的代码写的比较麻烦一些。

实现代码以及可运行的环境请从此处下载:

http://www.bluedavy.com/open/benchmark.zip

l  结果对比

测试机器为一台4核的linux机器。

TPS的对比结果如下:


    Load
的对比结果如下:
    

从上面的测试结果来看,在这个benchmark的场景中,基于KilimScala实现的Coroutine版本在随着请求数增长的情况下load的增长幅度都比纯粹的Java版本低很多,Kilim版本表现尤其突出,在TPS方面,由于目前Scala版本对Continuation支持的不好,因此在这个测试场景中有点吃亏,表现反而最差,经过上面的测试可以看到,基于Coroutine版本可以以同样的load或更低的load来支撑更高的TPS

到此为止,基本上对Java中使用Coroutine的相关知识做了一个介绍,总结而言,采用Coroutine方式可以很好的绕开需要启动太多线程来支撑高并发出现的瓶颈,提高Java应用所能支撑的并发量,但在开发模式上也会带来变化,并且需要特别注意不能造成线程被阻塞的现象,从开发易用和透明迁移现有Java应用两个角度而言目前Coroutine方式还有很多不足,但相信随着越来越多的人在Java中使用Coroutine,其易用性必然是能够得到提升的。

4.       参考资料

1.         http://en.wikipedia.org/wiki/Computer_multitasking

2.         http://en.wikipedia.org/wiki/Coroutine

3.         http://en.wikipedia.org/wiki/Actor_model

4.         http://en.wikipedia.org/wiki/Continuation

5.         http://lamp.epfl.ch/~phaller/doc/haller07coord.pdf

6.         http://www.scala-lang.org/sites/default/files/odersky/jmlc06.pdf

7.         http://www.malhar.net/sriram/kilim/kilim_ecoop08.pdf

8.         http://lamp.epfl.ch/~phaller/doc/ScalaActors.pdf



posted on 2010-01-28 23:16 BlueDavy 阅读(25517) 评论(27)  编辑  收藏 所属分类: Internet

评论

# re: 在Java中使用协程(Coroutine)[未登录] 2010-01-29 00:15 Shawn

>对于Java应用而言,传统方式下为了支持高并发,由于一个线程只能用于处理
>一个请求,即使是线程中其实有很多IO中断、锁等待也同样如此,因此通常的
>做法是通过启动很多的线程来支撑高并发,但当线程过多时,就造成了CPU需
>要消耗不少的时间在线程的切换上,从而出现瓶颈,按照上面对Coroutine的
>描述,Coroutine的方式理论上而言能够大幅度的提升Java应用所能支撑的并发量


BlueDavy, 我认为还有更底层的导致性能的关键,就是SMP下的网卡中断处理,即:当流量足够大的时候,网卡的中断处理可能成为吞吐量的关键,而此时不管用什么调度器可能都没太大帮助。

万兆网卡都是多个读写中断,并且绑定到具体的CPU的,但我们现在的千兆网卡都是绑定到一个CPU的,数据包过了网卡,轮到tcp/ip协议栈处理,然后轮到文件系统(socket fd),然后轮到poll/epoll机制,这些都是在内核完成的,我发现无论再多CPU,都只会stick在一块CPU上面完成,最后才轮到user space的代码,譬如jvm,才有机会分配到其他线程/CPU去执行。所以如果前面几个环节出现瓶颈问题,是否用Coroutine都救不了命。

这几天在跟踪线上CPU不均匀的问题,刚好把这些东西去了解了一下,有机会我们再当面交流。  回复  更多评论   

# re: 在Java中使用协程(Coroutine)[未登录] 2010-01-29 00:23 Shawn

@Shawn
md说了半天没说出我的观点。
我的观点是:如果能分配好网卡中断,用合适的内核调度器,用好内核的异步IO,选择合适的多线程模型,其实就算用多线程也可以达到很搞的吞吐量的。我现在就期盼java7的AIO支持了。  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 01:36 岑文初

占个坑,明天来看,哈哈哈,感谢分享  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 09:04 BlueDavy

@Shawn
同学,这个和内核异步IO其实还是有不同的,不同的地方在于一个请求的处理过程其实是需要消耗一个原生线程的,coroutine之类的好处其实就是做到真正的复用线程,避免在做等待等动作时仍然占据着线程。
当然,不可否认,网卡终端,内核调度这些也很重要,:)
ps: 不要去等待java 7的AIO这些,毕竟java 7离真正能商业使用还有距离,在短期内估计还是得想办法自己折腾,就像coroutine java7也开始支持。  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 09:05 glf

老大,有空也测一下Jetlang吧,另一个java的actor实现  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 09:06 argan

@Shawn
这只是一种场景而已,流量大,访问密集的,在其他场景下,比如有大量的链接上来,但是偶尔做点事情的时候Actor模型就好很多了,而且Actor模型也不只是为了处理网络来设计的,在其他很多场景也是适用的,怎么选择,总归还是要看场景的  回复  更多评论   

# re: 在Java中使用协程(Coroutine)[未登录] 2010-01-29 09:25 BT

@Shawn
Mina下面有个实验的东东
https://svn.apache.org/repos/asf/mina/sandbox/mheath/aioj/trunk

@BlueDavy
谢了,正好在查阅相关资料。  回复  更多评论   

# re: 在Java中使用协程(Coroutine)[未登录] 2010-01-29 09:39 BT

benchmark代码借用一下,加个akka的试试  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 09:47 BlueDavy

@BT
顶,期待结果!  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 09:48 BlueDavy

@argan
顶这位同学,coroutine对于java而言意义有点像从BIO走向NIO(当然,这只有量大的情况下才能体现),从每请求一个线程的时代不如多请求共用线程,充分发挥CPU的能力,避免消耗在无谓的切换上,:)  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 09:49 BlueDavy

@glf
OMG,还真不知道这东西,找时间我折腾下。  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 11:09 路过

你好,怎么测试你给的kilim例子会报以下错误:
############################################################
Task class coroutine.kilim.benchmark.Main$ReceiveTask has either not been woven or the classpath is incorrect
############################################################
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1158)
at kilim.Task.errNotWoven(Task.java:227)
at kilim.Task.execute(Task.java:289)
at kilim.Task._runExecute(Task.java:339)
at kilim.SchedulerBasedOnThreadPool$1.run(SchedulerBasedOnThreadPool.java:42)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
at java.lang.Thread.run(Thread.java:595)  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 12:45 BlueDavy

@路过
...是直接运行dist里的吗?

还是直接source导入eclipse里运行呢?如果source直接导入eclipse里运行的话,kilim版本是不行的,因为kilim需要对编译出来的class做weaver.  回复  更多评论   

# re: 在Java中使用协程(Coroutine)[未登录] 2010-01-29 16:53 paul

多个任务对应多个线程或进程这种处理方式,我想很典型的就是阻塞方式的socket。服务端一般都是来一个请求创建一个线程去处理任务。但是用nio解决了创建多个线程问题。或向mina这种socket框架也解决了这问题。不知道和你这个有啥区别?同其它相比好处又在那里?  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 19:23 darren

event -based的actor 跟thread-based 的actor结合而已, 取长补短嘛

BTW. "Actor Model简单来说就是每个任务就是一个Actor," ---对这句话感觉不妥.  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 20:21 melin

Groovy Actor  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-29 23:43 BlueDavy

@paul
...NIO解决的是可以做到实际请求一个线程来处理,但一旦这个线程中有需要切换出线程的操作时,但请求又是个同步请求时,那么这时能做的只能是继续占据这个原生的线程资源。
何况Coroutine不是仅仅用来解决网络通信这种场合的。  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-01-31 21:33 Oasis Feng

@Shawn
网卡基于中断的工作方式确实在网络请求密集型场景中会造成性能瓶颈,不过在大多数未经充分优化的互联网应用中是很难真正到达这个层面的。我们往往有更值得优先投入精力去优化的环节,比如利用协程改造密集分布式调用的性能。

Facebook的开发团队已经就网卡中断这个问题有过深入的分析和优化改造。可以参考他们的这篇blog:http://www.facebook.com/note.php?note_id=39391378919  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-02-20 17:09 simon.shen

另外一种方式则是每个任务不完全占据一个进程或线程,当任务执行过程中需要进行IO等待等动作时,任务则将其所占据的进程或线程释放,以便其他任务使用这个进程或线程

这样的话,线程释放的时候,当前任务的线程上下文怎么保存呢?  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-02-23 13:14 BlueDavy

@simon.shen
:),coroutine框架来保存。  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-03-05 21:01 simon.shen

:),coroutine框架来保存

那其实还是切换的,和一般的线程切换还是一样啊,保持线程的栈、pc计数器等。
  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-03-23 21:18 afei

@Shawn
千兆网卡是可以中断分布到多个cpu的
  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2010-06-18 17:42 Evan Wu

请问Java示例代码中,在main方法内的execute方法中写了一个Runnable来延迟5秒钟倒计时,并在业务逻辑所在的executor里运行了数百个此Runnable的实例,是出于什么考虑?  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2011-01-12 23:59 fuyou001

太晚了精力了,明天好好看看  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2011-02-26 00:04 landon

想问一下大牛:用堆栈打印异常信息时出现的Unknown Source是怎么回事?通常是进去jdk源码会出现这种情况。不过我在网上看到好多,即使是jdk源码也可以打印出所在的行数。  回复  更多评论   

# re: 在Java中使用协程(Coroutine) 2011-03-02 14:58 cuilike

@landon
编译的时候加上参数。
javac debug="true" debuglevel="lines,source"  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

公告

 









feedsky
抓虾
google reader
鲜果

导航

<2010年1月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

统计

随笔分类

随笔档案

文章档案

Blogger's

搜索

最新评论

阅读排行榜

评论排行榜