• Feeds

  • 使用脚本引擎增加程序运行时动态执行能力(Java篇)

    对于非Web的后台服务程序,经常会碰到这样的需求:

    1. 动态改变程序运行时参数的能力。如Config.limitValue = 50
    2. 动态查看运行时候变量状态的能力,如 print(userMap.size())
    3. 执行代码的能力,如 userMap.clear()

    对于需求1,非Web的程序没法像PHP/JSP那样直接改就生效,往往改了某个值,即使是一个配置参数都需要服务器重启一下。这对于很多线上服务来说成本太高。

    对于上述2的需求,通常是编写庞大的后台管理程序来满足。或者是通过增加日志输出,但问题是某些变量只需要偶尔才看一下,因此很多系统中大部分日志开销基本上是无用的。

    对于需求3,只有通过在IDE的debug模式下才能达到。

    我觉得所有的后台程序都应该具备这样的能力,为了满足以上需求,于是考虑在所有程序里面嵌入一个小型的解释脚本引擎来实现。就像很多游戏程序嵌入Lua/Python来动态执行代码目的一样。

    今天先说下Java中的实现方法,几年前用过BeanShell, 可以在Java VM里面解释执行多种类型脚本语言。但是发现已经多年不更新,原来是Java 6已经内建Scripting的支持,所以这些第三方的工具也都结束了历史使命。

    Java 6自带的实际上是 Mozilla Rhino 的Java Script引擎,它使用JavaScript的语法,可以调用任意Java代码。更多介绍见Java Scripting Programmer’s Guide. Rhino除了import写法有点特殊外,基本语法和Java代码基本一致。如:

    engine.eval("importClass(java.lang.System)");
    engine.eval("println(System.currentTimeMillis())");
    engine.eval("println('Source: https://timyang.net/java/java-scriptingjava-scripting/')");

    于是做了一个简单的socket服务器,把发过来的文本直接执行,然后再把脚本执行产生的内容返回给发送方。目标达成。共40余行代码(包括注释和花括号),无需任何第三方library。代码如下:

    // create a script engine manager
    ScriptEngineManager factory = new ScriptEngineManager();
    // create a JavaScript engine
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    
    while (true) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(4444, 50, InetAddress.getByName("localhost"));
        } catch (IOException e) {
            System.err.println("Could not listen on port: 4444.");
            System.exit(1);
        }
    
        Socket clientSocket = null;
        try {
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            System.err.println("Accept failed.");
            System.exit(1);
        }
    
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(),
                true);
        BufferedReader in = new BufferedReader(new InputStreamReader(
                clientSocket.getInputStream()));
        String inputLine, outputLine;
    
        while ((inputLine = in.readLine()) != null) {
            if ("quit".equals(inputLine))
                    break;
            try {
                // evaluate JavaScript code from String
                out.println(engine.eval(inputLine));
            } catch (Exception e) {
                out.println("error:" + e.getMessage());
            }
        }
        out.close();
        in.close();
        clientSocket.close();
        serverSocket.close();
    }

    启动程序之后只要 telnet localhost 4444 就可以执行任意代码并实时查看返回结果。

    注意上面为了安全起见,只绑定了localhost地址,防止此功能被外部用户恶意使用。

    了解Linux的锁与同步

    上周看了Linux的进程与线程,对操作系统的底层有了更进一步的一些了解。我同时用Linux内核设计与实现Solaris内核结构两本书对比着看,这样更容易产生对比和引发思考。现代操作系统很多思路都是相同的,比如抢占式的多线程及内核、虚拟内存管理等方面。但另外一方面还是有很多差异。在了解锁和同步之前,原子操作是所有一切底层实现的基础。

    原子操作Atomic

    通常操作系统和硬件都提供特性,可以对一个字节进行原子操作的的读写,并且通常在此基础上来实现更高级的锁特性。

    • atomic_t结构

    原子操作通常针对int或bit类型的数据,但是Linux并不能直接对int进行原子操作,而只能通过atomic_t的数据结构来进行。目前了解到的原因有两个。

    一是在老的Linux版本,atomic_t实际只有24位长,低8位用来做锁,如下图所示。这是由于Linux是一个跨平台的实现,可以运行在多种 CPU上,有些类型的CPU比如SPARC并没有原生的atomic指令支持,所以只能在32位int使用8位来做同步锁,避免多个线程同时访问。(最新版SPARC实现已经突破此限制)

    atomic_t

    另外一个原因是避免atomic_t传递到程序其他地方进行操作修改等。强制使用atomic_t,则避免被不恰当的误用。

    atomic_t my_counter = ATOMIC_INIT(0);
    val = atomic_read( &my_counter );
    atomic_add( 1, &my_counter );
    atomic_inc( &my_counter );
    atomic_sub( 1, &my_counter );
    atomic_dec( &my_counter );
    • 原子操作硬件上的实现

    Solaris的实现是基于test-and-set的指令,并且该指令为原子操作。比如Solaris的实现在SPARC上是基于ldstub和cas指令,在x86上用的是cmpxchg指令。但是Linux似乎直接用的add指令。

    OpenSolaris i386的实现

    	movl	4(%esp), %edx	/ %edx = target address
    	movl	(%edx), %eax	/ %eax = old value
    1:
    	leal	1(%eax), %ecx	/ %ecx = new value
    	lock
    	cmpxchgl %ecx, (%edx)	/ try to stick it in
    	jne	1b
    	movl	%ecx, %eax	/ return new value
    	ret

    在Linux源代码asm_i386/atomic.h中

    /**
     * atomic_add - add integer to atomic variable
     * @i: integer value to add
     * @v: pointer of type atomic_t
     *
     * Atomically adds @i to @v.  Note that the guaranteed useful range
     * of an atomic_t is only 24 bits.
     */
    static __inline__ void atomic_add(int i, atomic_t *v)
    {
            __asm__ __volatile__(
                    LOCK "addl %1,%0"
                    :"=m" (v->counter)
                    :"ir" (i), "m" (v->counter));
    }
    

    锁的类型

    • Spinlocks自旋锁

    如果锁被占用,尝试获取锁的线程进入busy-wait状态,即CPU不停的循环检查锁是否可用。自旋锁适合占用锁非常短的场合,避免等待锁的线程sleep而带来的CPU两个context switch的开销。

    • Semaphores信号量

    如果锁被占用,尝试获取锁的线程进入sleep状态,CPU切换到别的线程。当锁释放之后,系统会自动唤醒sleep的线程。信号量适合对锁占用较长时间的场合。

    • Adaptive locks自适应锁

    顾名思义,自适应锁就是上面两种的结合。当线程尝试申请锁,会自动根据拥有锁的线程繁忙或sleep来选择是busy wait还是sleep。这种锁只有Solaris内核提供,Linux上未见有相关描述。

    几种锁的性能比较(Windows操作系统下,第一种类似atomic_inc, 2,3类似spinlock, 4,5类似semaphore)

    7357
    (图片来源:Intel Software Network)

    Java synchronization

    再理论联系实际一下,看Java中的锁底层如何实现的。这篇关于JVM的Thin Lock, Fat Lock, SPIN Lock与Tasuki Lock中讲到Java synchronization实际上也是一种自适应锁。

    于是,JVM早期版本的做法是,如果T1, T2,T3,T4…产生线程竞争,则T1通过CAS获得锁(此时是Thin Lock方式),如果T1在CAS期间获得锁,则T2,T3进入SPIN状态直到T1释放锁;而第二个获得锁的线程,比如T2,会将锁升级(Inflation)为Fat Lock,于是,以后尝试获得锁的线程都使用Mutex方式获得锁。

    Java AtomicInteger的实现,似乎和Solaris的实现非常类似,也是一个busy wait的方式

       /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    
        /**
         * Atomically sets the value to the given updated value
         * if the current value {@code ==} the expected value.
         *
         * @param expect the expected value
         * @param update the new value
         * @return true if successful. False return indicates that
         * the actual value was not equal to the expected value.
         */
        public final boolean compareAndSet(int expect, int update) {
            // unsafe.compareAndSwapInt是用本地代码实现
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    Solaris

    感觉OpenSolaris在很多地方要比Linux优秀,Solaris在理论设计和实践上都非常优雅,而Linux内核很多地方似乎更偏工程实践方向一些。另外Solaris用来做学习操作系统更合适,它的mdb几乎无所不能。

    我在VirtualBox虚拟机上安装了OpenSolaris,非常容易安装,使用这个Minimal OpenSolaris Appliance OVF image for VirtualBox 2.2 简易方法,安装一个没有gui的版本,大约3分钟以内就可以装好。OpenSolaris安装软件和Ubuntu一样方便,使用 pkg install SUNWxxx 的命令,比如 pkg install SUNWcurl

    QCon Beijing qconbeijing全部演讲资料下载

    QCon Beijing 2009 qconbeijing slide download

    QCon全球企业开发大会(QCon Enterprise Software Development Conference)2009

    QCon 2009北京大会全部演讲资料下载 (包含pdf, ppt 等格式slide)

    永久链接:https://timyang.net/?p=136

    Update 2010/03/01: 目前Dropbox已经被封,请自行想办法解决。(点这里可申请Dropbox账号)
    Update 2010/05/11: 注意本文是2009年的演讲稿,QCon beijing 2010 的演讲稿官方尚未全部公布,部分下载见这里 http://www.cnblogs.com/coderzh/archive/2010/04/28/qcon-beijing-2010-ppt-slideshare.html

    http://dl.getdropbox.com/u/1080311/suncaoxipingqconbeijing-090423084840-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/augmentumshaorongqconbeijing-090423085848-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/vmwareliyanbingqconbeijing-090423084914-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/ebayrandyqconbeijing-090423080359-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/pmlvjianweiqconbeijing-090423081337-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/uelvweideqconbeijing-090423091515-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/alipaychengliqconbeijing-090423080430-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/sprinthenrikqconbeijing-090423081310-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/openwebdylanqconbeijing-090423091545-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/youkuqiudanqconbeijing-090423080809-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/azurewuyananqconbeijing-090423084537-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/outsoftingqconbeijing-090423081254-phpapp02.ppt 
    http://dl.getdropbox.com/u/1080311/qcon2009intro-090412122012-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/youdaodengyiqconbeijing-090423072448-phpapp02.ppt 
    http://dl.getdropbox.com/u/1080311/freewheeldianeqconbeijing-090423080513-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/reusearchpanjiayuqconbeijing-090423083708-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/collabortivecommercevisionwangqconbeijing-090423084739-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/tddmaitianzhiqconbeijing-090423083637-phpapp01.ppt 
    http://dl.getdropbox.com/u/1080311/QCon_Summary_Chinese.pdf 
    http://dl.getdropbox.com/u/1080311/flexmajianqconbeijing-090423090123-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/geowebmoxiezhangqconbeijing-090423090510-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/enterprisejavamaoxinshengqconbeijing-090423085404-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/doubanhongqiangningqconbeijing-090423080457-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/taobaoyuexuqiangqconbeijing-090423085411-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/aboutarchzhouaiminqconbeijing-090423080807-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/archqualitygaohuantangqconbeijing-090423080658-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/newinfoqchinaintro-090412122754-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/siemensliweiqconbeijing-090423080616-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/amazonjeffbarqconbeijing-090423083948-phpapp01.pdf 
    http://dl.getdropbox.com/u/1080311/ppwithagileyannhamonqconbeijing-090423081417-phpapp02.pdf 
    http://dl.getdropbox.com/u/1080311/jrubyluogudaoqconbeijing-090423085418-phpapp01.pdf