庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

    xmemcached发布1.10 RC2,这一版本的主要改进如下:
1、修复1.10-RC1以来发现的bug

2、添加对flush_all协议的支持,XMemcachedClient.flushAll方法用以使memcached的缓存数据项失效,这一方法有系列重载方法

 void     flushAll()
          使cache中所有的数据项失效,如果是连接多个节点的memcached,那么所有的memcached中的数据项都将失效
 void     flushAll(long timeout)
          使cache中所有的数据项失效,如果是连接多个节点的memcached,那么所有的memcached中的数据项都将失效


 
void     flushAll(java.net.InetSocketAddress address)
         
使指定memcached节点的数据项失效
 void     flushAll(java.net.InetSocketAddress address, long timeout)
         
使指定memcached节点的数据项失效

void     flushAll(java.lang.String host)
          使指定memcached节点的数据项失效
void     flushAll(java.lang.String host, long timeout)
          使指定memcached节点的数据项失效

使用方法,例子:

XMemcachedClientBuilder builder = new XMemcachedClientBuilder(
                    AddrUtil.getAddresses(
"192.168.0.121:12000 192.168.0.121:12001"));
XMemcachedClient client 
= builder.build();
// 使所有数据项失效
client.flushAll(); 
//使192.168.0.121:12000节点的数据项失效
client.flushAll("192.168.0.121:12000");


3、添加stats协议支持,用以查询某个memcached server的统计信息:
java.util.Map<java.lang.String,java.lang.String>     stats(java.net.InetSocketAddress address)
         
查看特定节点的memcached server统计信息
 java.util.Map<java.lang.String,java.lang.String>     stats(java.net.InetSocketAddress address, long timeout)
          查看特定节点的memcached server统计信息
 java.util.Map
<java.lang.String,java.lang.String>     stats(java.lang.String host)
          
查看特定节点的memcached server统计信息
 java.util.Map<java.lang.String,java.lang.String>     stats(java.lang.String host, long timeout)
          查看特定节点的memcached server统计信息

    查询结果以map的形式返回,map中的key-value映射与stats协议返回的一致。例子:

//查询192.168.0.121:12000节点的统计信息
Map<String,String> info=xmemcachedClient.stats("192.168.0.121:12000")

4、允许通过jmx监控XMemcachedClient的运行状况,并调整参数等。jmx监控默认未开启,可以通过

-Dxmemcached.jmx.enable=true -Dxmemcached.rmi.port=7077 -Dxmemcached.rmi.name=xmemcachedServer

的参数来启用,那么就可以通过
service:jmx:rmi:///jndi/rmi://[host]:7077/xmemcachedServer

来访问MBean。

XMemcached提供两个标准MBean,一个是net.rubyeye.xmemcached.impl.OptimiezerMBean,用于查看和调整性能参数;另一个是net.rubyeye.xmemcached.monitor.StatisticsHandlerMBean,用于查询memcached客户端的统计信息(注意跟memcached server的统计信息做区别,客户端的统计信息可能包括了多个节点)。默认统计未开启,可以通过

-Dxmemcached.statistics.enable=true

来开启客户端统计。更多信息请用jconsole访问即知。

  下载地址在这里




posted @ 2009-05-05 20:03 dennis 阅读(1544) | 评论 (0)编辑 收藏

    以下内容纯属虚构,如有雷同纯属巧合。
    今天在伟大、光荣的xlands group里闲聊的时候,阿宝同学提到在上海被隔离的墨西哥航班乘客们被安排在一家酒店,并且房间里提供上网服务。俺不大灵光的脑袋突然灵光一闪,冒出个疑问:生物病毒可以通过比特传播吗?如果生物病毒可以通过比特传播,那么互联网将成为世界上最大的恐怖网络。
    让我们来认真研究这个问题,网络上传播只能是0和1的组成的字节,流感的DNA可以编码成一个字节流,通过网络传输到每天机器上,然后组装成致命病毒,并通过空气传播感染人群。这里有几个问题:
1、DNA编码成字节流,数据量会不会太大?这个可以通过挑选特定的病菌来解决,或者下面要谈到的需要硬件厂商配合的方法。、

2、怎么在用户的机器上合成致命病菌?要知道字节序列是无生命的,一个解决办法就是需要硬件厂商来配合,在芯片或者网卡中预先通过某种高科技的控制器存储足量的合成病菌所需要的DNA、蛋白质等等。当网卡接收到特殊特征的字节序列(比如“你好他也好”)就启动这个高科技控制器,吭哧吭哧地合成病毒,然后通过电脑风扇传播到空气中。更进一步,硬件中可以预先存放一些致命病毒(咳咳,我觉的埃博拉病毒不错),然后在地球某个阴暗的角落(很可能是北极,变形金刚和超人都在那),一只黑手按下一个神奇的红色按钮,向全世界安装了此类硬件的联网机器发出指令释放病毒,你说世界会怎么样?
   
    这种可怕、恐怖、无敌的网络恐怖主义,似乎没有什么抵抗方法,俺这里再探讨下可行的预防方法:
1、永远不上网
2、机器使用一年后就淘汰,防止机器老朽病毒泄漏
3、使用龙芯牌CPU
4、山寨整机
5、移居火星

   本论文版权所有,如有抄袭,板砖伺候。



posted @ 2009-05-04 15:56 dennis 阅读(432) | 评论 (2)编辑 收藏

每个第一次构建分布式系统的人都可能会做出8个错误的对网络的假设:
1. The network is reliable
2. Latency is zero
3. Bandwidth is infinite
4. The network is secure
5. Topology doesn't change
6. There is one administrator
7. Transport cost is zero
8. The network is homogeneous

翻译过来就是:
1、网络是稳定可靠的
2、没有延迟
3、带宽无限
4、网络是安全的
5、网络拓扑不会改变
6、只有一个管理员
7、传输成本为0
8、网络是均匀的,现实是各种网络环境都有。

更多错误假设:
1、网络IO跟磁盘IO一样
    网络IO比之磁盘IO更不可预测、不可靠和不可控,网络IO包括了软硬件两方面的限制。
2、你与对端能够同步
  你无法假设对端是否关闭、接收到数据,这些通常需要你在应用协议里同步。
3、所有的错误都可以被检测到。
    很多错误例如对端关闭引起的读阻塞都需要应用层来处理。

4、资源无限可用。
5、应用可以无限等待一个远程服务
    任何大规模的应用都需要慎重设计超时、过期策略

6、远程服务总能响应及时。
7、只有单点失败
8、只有一个资源分配器
9、只有一个时间,也就是全局时间的问题。

posted @ 2009-05-02 13:02 dennis 阅读(1786) | 评论 (0)编辑 收藏

    最近读薛涌的《学而时习之》,知道这本书是因为在《南方周末》上看到。闲话不说,其实这本书的精华都在那个序里,咱水平很有限,谈谈我的看法。序言单刀直入地表示中国文化是失败,薛涌判断文化是否失败的一个标准很简单:

    几年前我去医院,当我的白人医生知道我是来自中国、而且研究中国历史后,马上告诉我他在大学读过明史的课,非常景仰中国文化。我当时听不出他到底是 出于客气还是出于真诚,干脆直率地告诉他我的看法:从现代历史的角度说,中国文化是个失败的文化,至少不能说是个成功的文化。对方听了很吃惊,马上拿出文 化相对主义那一套和我辩论。我知道医生惜时如金、无法在看病时开一个中国文化的讨论班,就单刀直入地问他:“我愿意我和我的孩子生活在这里。你希望你或你 的后代生活在那里吗?”他一时语塞。

    我判定中国文化成功与失败的标准就这么简单。我是个中国人,我的大夫是个白人。但是,我们是完 全平等的。我们中国人应该享受这些白人所享受的生活,比如有相当高的经济收入,在宪政之下拥有自己的政治权利,等等。同生而为人,凭什么人家有这些而我们 没有?凭什么在人家有这些而我们没有时,还不能说我们失败了?

    我第一次看到这个论调的时候很是震撼,乍一看似乎说的非常在理。这个标准确实简单,一个美国人,愿意跟我调换生活吗?先别喷,如果我的家人朋友在美国,那我肯定愿意去美国生活,因为至少那边没有瘦肉精,没有城管,不用打酱油。那么这个美国人愿意吗?那就很值的怀疑了。无论答案是肯不肯,这个迟疑本身就让薛涌胜利了。
    这个判断文化失败与否的标准简单粗暴,却似乎直接有效。然而我却产生个疑问,这个标准对比的当下,而当下的中国还有多少传统文化的影子很值的怀疑;如果要对比当下,可能拿台湾人跟美国人对比更有意义,台湾保存的中华传统肯定比大陆多的多,那么一个台湾人愿意跟美国人调换生活吗?你无法简单地下结论了。再远些,如果拿这个标准去对比古代,一个唐朝人愿意跟那个时期的欧洲人(咱就不跟美洲印第安人比了)调换生活吗?这时候的你可能更偏向咱中国文化了,毕竟是盛唐时期嘛。如果从更长的时间维度看,单纯按照这么个简单粗暴的标准来衡量,中国文明似乎还相当长时间内领先于西方文明嘛。因此,还是《莫以成败论文化/文明

posted @ 2009-05-01 23:40 dennis 阅读(484) | 评论 (0)编辑 收藏

update:紧急修复一个严重的bug,影响多节点memcached下的余数哈希分布。jmx监控正式启用。更多单元测试。

    XMemcached是一个基于java nio的memcached客户端。最新发布1.10-RC1版本,这个版本其实早就完成,一直没有环境来测试,本机测试没有多大价值。今天做了一个初步测试,在效率上已经超越了spymemcached最新的2.3.1版本,具体的测试数据请看下面,下载地址这里,更新了wiki

   1.10-RC1的主要改进:
1、性能优化,具体请参见wiki,在测试中已经超越了spymemcached最新的2.3.1版本
2、重构,引入Optimiezer、XMemcachedClientBuilder等类,并引入泛型方法,使API接口更趋实用性和便捷性。1.10rc1将不兼容1.0版本,具体API请参见javadoc。可以保证的是在1.10稳定后,xmemcached在API接口不会再有大的变动。
3、引入maven做项目构建,原来的ant构建仍然可用。
4、bug修复,具体参见issues报告,更多测试提高健壮性。
5、引入jmx监控,可以通过java -Dxmemcached.jmx.enable=true来启用jmx监控,jmx功能可以统计xmemcached的各种操作次数,以及优化参数调整等,更多信息请参考javadoc和wiki

   后续计划:
1.10版本的正式发布
1.11版本的开发工作,引入JMX监控。

    今天给出的是memcached存储java原生类型的效率测试,key和value都是字符串,memcached单节点跑在局域网内的服务器上,spymemcached使用的是2.3.1版本,xmemcached使用的是1.10-RC1,两者都是默认配置。具体图表请看下面及相应说明。
    首先是在我本机windows xp环境(因为主要看对比,机器环境不再具体说明),连接memcached服务器所做的测试,下面是xmemcached和spymemcached的set操作效率对比,纵坐标为TPS,横坐标为线程并发数(下同)。



    显然,两者的set操作效率在windows下极为接近。接下来是get操作的对比直方图:


   
    XMemcached在get操作上保持传统优势,并发越大,优势越明显。在测试了windows下两个客户端的表现,继续做linux下的测试数据,因为所用的机器是一台8核的牛机,因此TPS非常惊人。首先是set操作的对比:
    


    不用多说,xmemcached全面占优。再看get操作的对比:



    一个奇特的现象是在200线程并发的时候,spymemcached反而超过了xmemcached,这一现象反复测试还是如此。其他并发下,xmemcached全面占优,在达到500并发时,spymemcached的get很多超时,因此数据作废,而xmemcached由于可以设置get的操作的超时时间更长,因此仍然正常运行。
    从以上测试可以看出,1.10-RC1版本的XMemcached存储原生类型已经在windows和linux平台上的效率都超过了spymemcached。对于自定义对象和容器对象的存储测试也证明xmemcached的效率已经全面超越了spymemcached。很希望有更多的测试报告,毕竟我的测试可能还是不够客观。



posted @ 2009-04-28 21:50 dennis 阅读(1730) | 评论 (2)编辑 收藏


1、在构造函数中启动线程
     我在很多代码中都看到这样的问题,在构造函数中启动一个线程,类似这样:
public class A{
   
public A(){
      
this.x=1;
      
this.y=2;
      
this.thread=new MyThread();
      
this.thread.start();
   }
   
}
   这个会引起什么问题呢?如果有个类B继承了类A,依据java类初始化的顺序,A的构造函数一定会在B的构造函数调用前被调用,那么thread线程也将在B被完全初始化之前启动,当thread运行时使用到了类A中的某些变量,那么就可能使用的不是你预期中的值,因为在B的构造函数中你可能赋给这些变量新的值。也就是说此时将有两个线程在使用这些变量,而这些变量却没有同步。
   解决这个问题有两个办法:将A设置为final,不可继承;或者提供单独的start方法用来启动线程,而不是放在构造函数中。

2、不完全的同步
  
都知道对一个变量同步的有效方式是用synchronized保护起来,synchronized可能是对象锁,也可能是类锁,看你是类方法还是实例方法。但是,当你将某个变量在A方法中同步,那么在变量出现的其他地方,你也需要同步,除非你允许弱可见性甚至产生错误值。类似这样的代码:
class A{
  
int x;
  
public int getX(){
     
return x;
  }
  
public synchronized void setX(int x)
  {
     
this.x=x;
  }
}
    x的setter方法有同步,然而getter方法却没有,那么就无法保证其他线程通过getX得到的x是最新的值。事实上,这里的setX的同步是没有必要的,因为对int的写入是原子的,这一点JVM规范已经保证,多个同步没有任何意义;当然,如果这里不是int,而是double或者long,那么getX和setX都将需要同步,因为double和long都是64位,写入和读取都是分成两个32位来进行(这一点取决于jvm的实现,有的jvm实现可能保证对long和double的read、write是原子的),没有保证原子性。类似上面这样的代码,其实都可以通过声明变量为volatile来解决。
  
3、在使用某个对象当锁时,改变了对象的引用,导致同步失效。
   这也是很常见的错误,类似下面的代码:
synchronized(array[0])
{
   ......
   array[0]=new A();
   ......
}
   同步块使用array[0]作为锁,然而在同步块中却改变了array[0]指向的引用。分析下这个场景,第一个线程获取了array[0]的锁,第二个线程因为无法获取array[0]而等待,在改变了array[0]的引用后,第三个线程获取了新的array[0]的锁,第一和第三两个线程持有的锁是不一样的,同步互斥的目的就完全没有达到了。这样代码的修改,通常是将锁声明为final变量或者引入业务无关的锁对象,保证在同步块内不会被修改引用。

4、没有在循环中调用wait()。

    wait和notify用于实现条件变量,你可能知道需要在同步块中调用wait和notify,为了保证条件的改变能做到原子性和可见性。常常看见很多代码做到了同步,却没有在循环中调用wait,而是使用if甚至没有条件判断:
synchronized(lock)
{
  
if(isEmpty()
     lock.wait();
   
}
    对条件的判断是使用if,这会造成什么问题呢?在判断条件之前可能调用notify或者notifyAll,那么条件已经满足,不会等待,这没什么问题。在条件没有满足,调用了wait()方法,释放lock锁并进入等待休眠状态。如果线程是在正常情况下,也就是条件被改变之后被唤醒,那么没有任何问题,条件满足继续执行下面的逻辑操作。问题在于线程可能被意外甚至恶意唤醒,由于没有再次进行条件判断,在条件没有被满足的情况下,线程执行了后续的操作。意外唤醒的情况,可能是调用了notifyAll,可能是有人恶意唤醒,也可能是很少情况下的自动苏醒(称为“伪唤醒”)。因此为了防止这种条件没有满足就执行后续操作的情况,需要在被唤醒后再次判断条件,如果条件不满足,继续进入等待状态,条件满足,才进行后续操作。
synchronized(lock)
{
   
while(isEmpty()
     lock.wait();
   
}
    没有进行条件判断就调用wait的情况更严重,因为在等待之前可能notify已经被调用,那么在调用了wait之后进入等待休眠状态后就无法保证线程苏醒过来。

5、同步的范围过小或者过大。

    同步的范围过小,可能完全没有达到同步的目的;同步的范围过大,可能会影响性能。同步范围过小的一个常见例子是误认为两个同步的方法一起调用也是将同步的,需要记住的是Atomic+Atomic!=Atomic。
   Map map=Collections.synchronizedMap(new HashMap());
  
if(!map.containsKey("a")){
            map.put(
"a", value);
   }
   这是一个很典型的错误,map是线程安全的,containskey和put方法也是线程安全的,然而两个线程安全的方法被组合调用就不一定是线程安全的了。因为在containsKey和put之间,可能有其他线程抢先put进了a,那么就可能覆盖了其他线程设置的值,导致值的丢失。解决这一问题的方法就是扩大同步范围,因为对象锁是可重入的,因此在线程安全方法之上再同步相同的锁对象不会有问题。
       Map map = Collections.synchronizedMap(new HashMap());
      
synchronized (map) {
            
if (!map.containsKey("a")) {
                map.put(
"a", value);
            }
        }
   注意,加大锁的范围,也要保证使用的是同一个锁,不然很可能造成死锁。 Collections.synchronizedMap(new HashMap())使用的锁是map本身,因此没有问题。当然,上面的情况现在更推荐使用ConcurrentHashMap,它有putIfAbsent方法来达到同样的目的并且满足线程安全性。

    同步范围过大的例子也很多,比如在同步块中new大对象,或者调用费时的IO操作(操作数据库,webservice等)。不得不调用费时操作的时候,一定要指定超时时间,例如通过URLConnection去invoke某个URL时就要设置connect timeout和read timeout,防止锁被独占不释放。
同步范围过大的情况下,要在保证线程安全的前提下,将不必要同步的操作从同步块中移出。

6、正确使用volatile
    在jdk5修正了volatile的语义后,volatile作为一种轻量级的同步策略就得到了大量的使用。volatile的严格定义参考jvm spec,这里只从volatile能做什么,和不能用来做什么出发做个探讨。

volatile可以用来做什么?

1)状态标志,模拟控制机制。常见用途如控制线程是否停止:
private volatile boolean stopped;
public void close(){
   stopped
=true;
}

public void run(){

   
while(!stopped){
      
//do something
   }
   
}
  

    前提是do something中不会有阻塞调用之类。volatile保证stopped变量的可见性,run方法中读取stopped变量总是main memory中的最新值。

2)安全发布,如修复DLC问题,具体参考http://www.javaeye.com/topic/260515和http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html
    private volatile IoBufferAllocator instance;
    
public IoBufferAllocator getInsntace(){
        
if(instance==null){
            
synchronized (IoBufferAllocator.class) {
                
if(instance==null)
                    instance
=new IoBufferAllocator();
            }
        }
        
return instance;
    }

3)开销较低的读写锁
public class CheesyCounter {
    private volatile int value;

    
public int getValue() { return value; }

    
public synchronized int increment() {
        
return value++;
    }
}

synchronized保证更新的原子性,volatile保证线程间的可见性。

volatile不能用于做什么?
1)不能用于做计数器
    public class CheesyCounter {
        
private volatile int value;

        
public int getValue() { return value; }

        
public int increment() {
            
return value++;
        }
    }

    因为value++其实是有三个操作组成的:读取、修改、写入,volatile不能保证这个序列是原子的。对value的修改操作依赖于value的最新值。解决这个问题的方法可以将increment方法同步,或者使用AtomicInteger原子类。

2)与其他变量构成不变式
   一个典型的例子是定义一个数据范围,需要保证约束lower<upper。
public class NumberRange {
    
private volatile int lower, upper;

    
public int getLower() { return lower; }
    
public int getUpper() { return upper; }

    
public void setLower(int value) { 
        
if (value > upper) 
            
throw new IllegalArgumentException();
        lower 
= value;
    }

    
public void setUpper(int value) { 
        
if (value < lower) 
            
throw new IllegalArgumentException();
        upper 
= value;
    }
}

    尽管讲lower和upper声明为volatile,但是setLower和setUpper并不是线程安全方法。假设初始状态为(0,5),同时调用setLower(4)和setUpper(3),两个线程交叉进行,最后结果可能是(4,3),违反了约束条件。修改这个问题的办法就是将setLower和setUpper同步:
public class NumberRange {
    
private volatile int lower, upper;

    
public int getLower() { return lower; }
    
public int getUpper() { return upper; }

    
public synchronized void setLower(int value) { 
        
if (value > upper) 
            
throw new IllegalArgumentException();
        lower 
= value;
    }

    
public synchronized void setUpper(int value) { 
        
if (value < lower) 
            
throw new IllegalArgumentException();
        upper 
= value;
    }
}

 

posted @ 2009-04-25 12:21 dennis 阅读(3200) | 评论 (2)编辑 收藏

1、散列表要解决的一个问题就是散列值的冲突问题,通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。java.util.HashMap采用的链表法的方式,链表是单向链表,因此在删除过程中要自己维持prev节点,我想不采用双向链表是从节省空间考虑。一个典型的查找过程:
for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e 
!= null;
             e 
= e.next) {
            Object k;
            
if (e.hash == hash &&
                ((k 
= e.key) == key || (key != null && key.equals(k))))
                
return e;
 }
   HashMap采用链表法而不是开放地址法,猜想可能的原因是从实用角度出发,对空间和时间效率做出的折中选择。采用开放地址法,无论是线性探测或者二次探测都可能造成群集现象,而双重散列会要求散列表的装填程度比较低的情况下会有比较好的查找效率,容易造成空间的浪费。

2、什么是负载因子?负载因子a定义为
     a=散列表的实际元素数目(n)/ 散列表的容量(m)

负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是 O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。

回到HashMap的实现,HashMap中的loadFactor其实定义的就是该map对象允许的最大的负载因子,如果超过这个系数将重新resize。这个是通过threshold字段来判断,看threshold的计算:
threshold = (int)(capacity * loadFactor);

结合上面的负载因子的定义公式可知,threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。注意到的一点是resize的规模是现有 capacity的两倍:
  if (size++ >= threshold)
            resize(
2 * table.length);
 
3、可能你也注意到了,java.util.HashMap对key的hash值多做了一步处理,而不是直接使用hashCode:

static int hash(int h) {
        h 
^= (h >>> 20^ (h >>> 12);
        
return h ^ (h >>> 7^ (h >>> 4);
  }

这个处理的原因在于HashMap的容量总是采用2的p次幂,而取index(槽位)的方法是
static int indexFor(int h, int length) {
        
return h & (length-1);
 }

这一运算等价于对length取模,也就是
       h % 2^p
返回的将是h的p个最低位组成的数字,我们假设hash输入是符合简单一致散列,然而这一假设并不能推论出hash的p个最低位也会符合简单一致散列,也许h的这p个最低位相同的几率很大,那么冲突的几率就非常大了。优秀的散列函数应该需要考虑所有的位。

因此为了防止这些“坏”的散列函数造成效率的降低,HashMap预先对hash值做了处理以考虑到所有的位,根据注释也可以知道。这个处理我看不懂,留待高人解释,也许来自于某本算法书也不一定。

4、我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略(速错),这一策略在源码中的实现是通过 modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount,

 HashIterator() {
            expectedModCount 
= modCount;
            
if (size > 0) { // advance to first entry
                Entry[] t = table;
                
while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了map

  
final Entry<K,V> nextEntry() {
            
if (modCount != expectedModCount)
                
throw new ConcurrentModificationException();
 注意到modCount声明为volatile,保证线程之间修改的可见性。
 




posted @ 2009-04-15 12:33 dennis 阅读(4377) | 评论 (0)编辑 收藏

    小毅同学最近有个烦心事,就是他的脑袋有点偏,由于长期脑袋歪向右边睡,导致他的大脑袋右扁左高,从上往下看,极
其不自然。这个问题让他爷爷奶奶、爸爸妈妈很烦心,最近计划着给他买个定型枕,三个月了,可以睡枕头了,趁还矫正得过
来的时候赶紧矫正,他爸爸也叮嘱妈妈要让他侧睡,一个晚上仰躺,一个晚上左侧睡,希望能慢慢好起来。不然以后找不到媳
妇可能要怨死他老妈。
    小毅同学也有个非常不好的习惯,要睡觉的时候不是乖乖睡觉,而是拼命哭闹,非要别人抱着睡。抱着睡也还罢了,还要求
抱的人不能坐不能停,一停或坐就又开始挺直了身体不舒坦起来了,必须在屋内走来走去,看他那小脸,很喜欢别人抱着一晃一
晃的感觉。小毅同学睡觉也是很有趣,一开始是假寐,时刻注意抱的人是否停留,偶尔张开眼瞄你一下马上闭上,我说他这是在
观察敌情呢。过那么一会,眼睛不睁了,那时候你就可以停下来坐着抱了,但是这时候还不能马上放床上,你一放床上他也会发
觉醒过来,你不得不重复“散步”这一阶段,因此最好是坐着抱他一会,等他睡熟了再放床上去。
  小毅同学刚出生到2个月的时候,基本不笑,偶尔笑也不是对着你笑,而是对着空气笑,让人恨的牙痒痒,他妈妈说他这是对
花笑,“对花笑”这词还真有点佛意在里面。现在三个月了,开始会对着你笑,不过有个问题,他很少对着抱他的人笑,反而不
抱他的人逗他一下就咧开了小嘴笑嘻嘻。小毅同学的哭也很有趣,头两个月的哭是真哭,那种脸红脖子粗、眼泪豆大豆大往下掉
的哭,现在哭呢,就是把眼睛咪上,“扮”出一副委屈样,嘴里哼着我称之为咕噜咕噜语的声音,可怜兮兮地看着你,等着你抱
他;你要是不抱,他立马“加大音量”,此时他爷爷奶奶就撑不住了,赶紧把他抱起来,如果是我,继续不抱的话,他马上开始
掉眼泪了,此时才是“真哭”呀。


posted @ 2009-03-27 09:11 dennis 阅读(2741) | 评论 (2)编辑 收藏

     摘要:     受javaeye上的《Ruby中文编程》启发,帖子中有人提到如果if这样的关键字都可以定义成中文,那就是真正的中文编程。那时我就想到,这个其实要在scheme中实现是多么简单,将sicp书中的解释器稍微修改下就可以了,只要修改解析的部分即可。解释器的完整代码放后面,我们先看看有趣的例子: Code highlighting produced by A...  阅读全文

posted @ 2009-03-20 23:27 dennis 阅读(3113) | 评论 (8)编辑 收藏

本图不针对任何网站和个人,仅仅是因为很离谱。





posted @ 2009-03-19 19:04 dennis 阅读(1365) | 评论 (3)编辑 收藏

仅列出标题
共56页: First 上一页 17 18 19 20 21 22 23 24 25 下一页 Last