月度归档:2014年05月

scala雾中风景(13): 模式匹配中的逻辑或

bigbull_提的问题,说在看akka的源码时,模式匹配有这样的用法,之前没有看到过:

def isManagementMessage(msg: Any): Boolean = 
  msg match { 
    case _: AutoReceivedMessage | _: Terminated | _: RouterManagementMesssage ⇒ true 
    case _ ⇒ false 
  }

自己尝试:

val a = 1 
val b = a match { case _:String | _:Int => "str or int" } 

却报错。

这是模式匹配可以对多个条件一起匹配的情况。假设有2个case class定义如下:

scala> case class A(p1:Int, p2:Int)
defined class A

scala> case class B(p1:String, p2:String)
defined class B

现在我们想在模式匹配的时候,判断目标是否匹配A或B,可以这样写:

scala> def foo(msg:Any) = msg match { case A(_,2) | B(_,_) => println("ok") }
foo: (msg: Any)Unit

scala> foo(A(1,2))
ok

上面使用的是构造器模式混合通配符,如果我们不关心匹配时解构参数,只关心类型,可以用下面的方式来写:

scala> def foo(msg:Any) = msg match { case _:A | _:B => println("ok") }
foo: (msg: Any)Unit

scala> foo(A(1,2))
ok

这里注意,上面的类型匹配不能简化为 case _: A | B 因为这样写B代表的是伴生对象,是常量匹配:

scala> def foo(msg:Any) = msg match { case _:A | B => println("ok") }
foo: (msg: Any)Unit

scala> foo(A(1,2))
ok

scala> foo(B(1,2))
<console>:13: error: type mismatch;
found   : Int(1)
required: String
          foo(B(1,2))
                ^
scala> foo(B)
ok

上面的匹配表达式表示匹配A类型,或者B伴生对象(常量)。

而原先测试时,使用的

scala> val a = 1  // 这里已经推导a为Int类型
scala> val b = a match { case _:String | _:Int => "str or int" }
<console>:8: error: scrutinee is incompatible with pattern type;

这其实也可以给出警告而不是错误,scala编译器比较严格直接报错误了,要通过编译,可以把a的类型设置的泛一些:

scala> val a:Any = 1
a: Any = 1

scala> val b = a match { case _:String | _:Int => "str or int" }
b: String = str or int  

李昌钰博士的演讲

下午在公司的报告厅,听了李昌钰博士的《我的鑑識生涯—世际名案与學思歷程》,以前从未了解过这个行业,李博士的演讲也蛮有趣的,还记得几点:

1) 他提到昨天到广州,进酒店时已经晚上11点,准备今天的ppt和演讲到晚上2点,然后又坚持写书(每天写一页),虽然他这一页写的少了些只有几行,但让自己心安。

2) 证人的信息有40%是不可靠的。

3) 关于陈水扁枪击案,子弹水平划过肚皮,很多人觉得像是刀伤,但是有灼烧痕迹的。这是通过一把玩具枪改造的手枪,做枪的人把里面的枪栓换掉,这种枪杀伤力不大,但在短距离内还是有一些威胁的。使用自制子弹。打中吕秀莲腿部的是铜子弹,打中陈水扁的是铅子弹。嫌疑犯后来落水死亡,这个案子仍未结案。

4) 他的父亲是1949年乘坐太平轮从上海去往台湾时遇难的

家境原本还算富裕,父亲遇难后变得比较困难,所以他后来读书时选择读免费的警官学校。可能也是这个原因使他一直很尊敬和听从他妈妈的话,他演讲过程中提到过好几次别人邀请他做某个位置的职务时,他起先并不愿意,后来对方通过他的妈妈说服他。

tomcat每隔一小时full gc的问题

关于gc信息的收集,除了在启动时设置gclog选项,对于已经启动的java进程,也可以通过jstat去查看gc发生的原因。

$ jstat -gccause  java_pid 1s

S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
0.00 100.00  31.17  74.24  98.80      7    0.045     1    0.176    0.221 Allocation Failure   No GC
0.00 100.00  32.61  74.24  98.83      7    0.045     1    0.176    0.221 Allocation Failure   No GC
73.65   0.00  0.00  74.24  99.73      8    0.052     2    0.176    0.228 No GC      System.gc()
0.00   0.00   1.44  87.57  65.40      8    0.052     2    0.395    0.447 System.gc()          No GC
0.00   0.00   1.44  87.57  65.40      8    0.052     2    0.395    0.447 System.gc()          No GC
0.00   0.00   1.44  87.57  65.40      8    0.052     2    0.395    0.447 System.gc()          No GC

有一些应用周期性的发生full gc,即使没有多大访问量的情况,在tomcat里可能有2种原因:

1) 预防内存泄露的监听器 JreMemoryLeakPreventionListener 每隔一小时触发一次System.gc

Class clazz = Class.forName("sun.misc.GC");
Method method = clazz.getDeclaredMethod(
                        "requestLatency",
                        new Class[] {long.class});
method.invoke(null, Long.valueOf(3600000)); // 一小时

去看sun.misc.GCrequestLatency方法,传入的参数表示一次gc请求的延迟时间。会有个低优先级的daemon线程设置一个不超过这个延迟时间的object-inspection的周期

/**
 * Makes a new request for a garbage-collection latency of the given
 * number of real-time milliseconds.  A low-priority daemon thread makes a
 * best effort to ensure that the maximum object-inspection age never
 * exceeds the smallest of the currently active requests.
 *
 * @param   latency
 *          The requested latency
 *
 * @throws  IllegalArgumentException
 *          If the given <code>latency</code> is non-positive
 */
public static LatencyRequest requestLatency(long latency) {
    return new LatencyRequest(latency);
}

Daemon线程里,会在object-inspection周期之后执行System.gc()的。在tomcat 7028 和 6036 之后的版本里,把延迟时间调整了,不会再每小时调用一次了:

method.invoke(null, Long.valueOf(Long.MAX_VALUE - 1));

2) rmi.dgc 在JDK6里默认是1小时

参考这里,可以通过系统参数修改:

-Dsun.rmi.dgc.client.gcInterval=72000000
-Dsun.rmi.dgc.server.gcInterval=72000000

最近看过的电影(2)

《枪声俱乐部》(The Bang Bang Club)是无意中发现的一部值得一看的电影,让世界震惊的那张关于苏丹的照片:“秃鹫在一旁等待垂死的小女孩”的那张,就是出自这群战地摄影师。他们中有两位获得了普利策奖,当然他们也承受了巨大的代价。

最近看过的电影(1)

在伊朗电影《梅子鸡的味道》里面,讲述了一个小提琴艺术家在自杀前用八天回顾他的一生,在倒数第二天还是第三天的时候他看到了死神,死神跟他聊了会儿天,说你见到我并不代表你马上就要死,给他讲了另外一个人的故事:

一天死神在耶路撒冷的一个集市上看到一个人,就去跟这个人打了个招呼。把这个人给吓坏了,跑到了所罗门王那里去求救。所罗门王说碰到死神是无法逃避的,无奈此人百般请求,只好用法术把他送去了印度。等他到达印度后,死神再次碰到他,对他说“这次时间刚刚好”;这个人就问,为什么你在耶路撒冷的时候不让我死?死神回答,你应该是一天后死的,我只是好奇,想看看你这一天能走多远。

这个故事很多年前曾在一本杂志上看到过另一个版本:一个仆人去集市上买菜,碰到了魔鬼撒旦,撒旦对着他诡异的微笑,把他吓坏了,赶紧跑回家告诉了他的主人。他的主人说这不是个好征兆,于是给了他一匹最快的马,让他连夜赶往麦加,去他的亲戚家避一避。仆人走了之后,主人到集市上来找撒旦,问他为什么要吓唬他的仆人。撒旦回答:我也奇怪,在生死薄上他应该是晚上死在麦加的,离这里很远,时辰快要到了,他却怎么还在这里?

GreenTea JUG的交流

周六下午参加了在支付宝举办的GreenTea JUG交流

Oracle的Simon是这次的主要嘉宾,他分享的主题是《JAVA8:Create The Future》,我下午有些事情到达的时候他已经快讲完了,之后Simon因为要赶飞机,没有太多交流。

有人提到Java8里Optional类型的意义,这点的确是java程序员难以一下子体会的,Java8里的Stream, Optional都是从函数式风格语言里借鉴过来的,有些monad的味道

空冥分享了《HSF2.0》,外部可能对阿里开源的Dubbo更熟悉,实际内部使用HSF更多一些,当然现在HSF1.x和Dubbo都融合成了 HSF2.0

我这次分享了《Ali-Tomcat》,一半内容是apache-tomcat的基本架构和运行方式,另一半内容是阿里内部如何使用,以及我们增加和裁剪掉的功能。ppt可以从slideshareweibo下载。

java nio channel抛出ClosedByInterruptException的情况

java nio里的channel是实现自InterruptibleChannel接口的,这个接口的注释里有说明,当正在操作这个channel的线程被其他线程中断,则会close这个channel,当前(被中断的)线程抛出一个ClosedByInterruptException异常。

我们今天在排查一个问题时,用户线程执行了下面的调用过程(从上往下):

org.apache.catalina.connector.CoyoteOutputStream.flush ---》
org.apache.tomcat.util.net.NioChannel.write ---》
sun.nio.ch.SocketChannelImpl.write ---》
java.nio.channels.spi.AbstractInterruptibleChannel.end  // 这里抛出异常

来看一下这个sun.nio.ch.SocketChannelImpl.write 方法内部,它的详细代码可以看这里) 这里简化一些:

 public int  write(ByteBuffer buf) throws IOException {
  ...
  synchronized (writeLock) {
      ...
      try {
          begin();
          ...
      } finally {
          ...
          end(n > 0 || (n == IOStatus.UNAVAILABLE));
          ...
      }
  }
}

主要看一下它里面的beginend,先看end方法,异常抛出的地方:

protected final void end(boolean completed)
    throws AsynchronousCloseException
{
    blockedOn(null);
    Thread interrupted = this.interrupted;
    if (interrupted != null && interrupted == Thread.currentThread()) {
        interrupted = null;
        throw new ClosedByInterruptException();  
    }
    if (!completed && !open)
        throw new AsynchronousCloseException();
}

可以看到ClosedByInterruptException异常抛出的前提是当前线程被标记为已中断的;而这个判断是在begin方法里做的:

protected final void begin() {
    if (interruptor == null) {
        interruptor = new Interruptible() {
                public void interrupt(Thread target) {
                    synchronized (closeLock) {
                        if (!open)
                            return;
                        open = false;
                        interrupted = target;
                        try {
                            AbstractInterruptibleChannel.this.implCloseChannel();
                        } catch (IOException x) { }
                    }
                }};
    }
    blockedOn(interruptor);
    Thread me = Thread.currentThread();
    if (me.isInterrupted()) // 检测当前线程是否已中断
        interruptor.interrupt(me);
}

begin方法里,检查当前线程如果是中断状态,用引用记录起来(为了后边比较使用),并关闭了channel。

现在用scala模拟一下这个异常:

$ cat server

import java.nio._
import java.net._
import java.nio.channels._

val serverSock = ServerSocketChannel.open()
serverSock.socket().bind(new InetSocketAddress(54321))

val  ch:SocketChannel = serverSock.accept()

println("ok,received")

Thread.currentThread().interrupt() //中断当前线程
try{
    ch.socket().getOutputStream().write(200)
}catch{
    case e:Throwable => println(e)
}

上面的这段脚本,用nio模拟了一个server等待客户端的链接,当链接过来的时候,中断当前线程,然后继续channel进行处理的时候会捕获到ClosedByInterruptException异常。

启动上面的脚本

$ scala server

在另一个终端下模拟一次client请求:

$ telnet localhost 54321

这时会看到server端的输出信息:

$ scala server
ok,received
java.nio.channels.ClosedByInterruptException