• 反模式之流失监听者泄露(Lapsed Listener Leak)

    有这样一段代码:

    public class LapsedListenerTest {
    
        public static class A{
            private List listeners = new CopyOnWriteArrayList();
            private String name;
            public A(String name) {
                this.name = name;
            }
            public void addListener(PropertyChangeListener listener) {
                listeners.add(listener);
            }
            private void fire(String property,Object oldValue,Object newValue) {
                for(PropertyChangeListener listener:listeners) {
                        listener.propertyChange(new PropertyChangeEvent(this, property, oldValue, newValue));
                }
            }
            public void setName(String name) {
                String oldName = this.name;
                this.name = name;
                fire("name", oldName, name);
            }
        }
    
        public static class B implements PropertyChangeListener{
            private int id;
            private A a;
            //占用内存
            private byte[] bt= new byte[1024*1024];
            public B(A a,int id) {
                this.id = id;
                this.a = a;
                this.a.addListener(this);
            }
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("on B-" B.this.id "(" evt.getPropertyName() "," evt.getOldValue() "," evt.getNewValue() ")");
            }
        }
    
        /**
         * @param args
         * @throws InterruptedException
         */
         public static void main(String[] args) throws InterruptedException {
            A a = new A("A");
            int j = 0;
            while(true) {
                B b = new B(a,j);
                //do some thing with b
                a.setName("A" j);
                b.dispose();
                j  ;
                System.gc();
                Thread.sleep(20);
            }
        }
    
    } 
    

     

    代码片段1

    A类的对象生命周期都比较长,而B类的对象生命周期都比较短,它需要监听A类的属性变化而进行一些操作。这个程序运行一下,很快就内存溢出了。

    这里明显存在一个反模式:流失监听者泄露(Lapsed Listener Leak)。 由于B类的对象的生命周期比较短,如果在系统运行过程中,大量生成B类的对象,而B的构造函数中有将自己作为监听器加入A的监听器列表中,导致B类的对象无法被垃圾回收器回收。在这里,B自己作为监听器,错误看起来比较明显,但如果用内部匿名类的实现方式:

     

    public B(A a,int id) {
                this.id = id;
                this.a = a;
                this.a.addListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        System.out.println("on B-" B.this.id "(" evt.getPropertyName() "," evt.getOldValue() "," evt.getNewValue() ")");
                    }
                });
            }
    

     

    代码片段2

    看起来虽然没那么明显,但效果是一样的。因为匿名类的对象会引用自己的父对象。

     

    这个反模式在Java用户界面框架(AWT,Swing)使用的过程中出现的比较多,但没出现问题的原因主要有两个:1.桌面软件的运行时间一般不会太长,很少有需要7X24运行的。2.桌面类软件中的组件数目有限,一般也不会导致内存溢出。但如果是服务器端的程序就没这么幸运了。

    《Bitter Java》中给出了三种解决方案:

    1.显式的删除监听者

    在A中增加removeListener接口,当B的生命周期结束后,将其从A的监听器列表里删除。

    2.缩短锚的生命周期

    确保A类对象的生命周期不要太长,A类的对象如果能尽快被垃圾回收器回收,内存泄露也就不会发生了。

    3.弱化引用

    用弱引用(WeakReference)关联监听器。弱引用的好处是,如果垃圾回收器检测到一个对象当前只有弱引用,就认为该对象可以被回收了。下面是用弱引用重构后的实例代码:

     

    public class LapsedListenerTest2 {
    
        public static class A{
            private List listeners = new CopyOnWriteArrayList();
            private String name;
            public A(String name) {
                this.name = name;
            }
            public void addListener(PropertyChangeListener listener) {
                listeners.add(new WeakReference(listener));
            }
            private void fire(String property,Object oldValue,Object newValue) {
                for(WeakReference listenerRef:listeners) {
                    PropertyChangeListener listener = listenerRef.get();
                    if(listener != null) {
                        listenerRef.get().propertyChange(new PropertyChangeEvent(this, property, oldValue, newValue));
                    }else {
                        listeners.remove(listenerRef);
                    }
                }
            }
            public void setName(String name) {
                String oldName = this.name;
                this.name = name;
                fire("name", oldName, name);
            }
        }
    
        public static class B implements PropertyChangeListener{
            private int id;
            private A a;
            private byte[] bt= new byte[1024*1024];
    
            public B(A a,int id) {
                this.id = id;
                this.a = a;
                this.a.addListener(this);
            }
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("on B-" B.this.id "(" evt.getPropertyName() "," evt.getOldValue() "," evt.getNewValue() ")");
            }
            @Override
            protected void finalize() throws Throwable {
                System.out.println("finalize:" id);
            }
        }
    
        /**
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            A a = new A("A");
            int j = 0;
            B first = null;
            while(true) {
                B b = new B(a,j);
                if(first == null) {
                    first = b;
                }
                //do some thing with b
                a.setName("A" j);
                j  ;
                System.gc();
                Thread.sleep(20);
            }
        }
    }
    

     

    代码片段3

     运行后可以看出:

    由于first引用了第一个B对象,致使该对象一直存活,而其他的B对象都被很快的回收了。是不是一个完美的解决方案?别着急,这儿还有个问题。如果B类采用匿名类监听器的情况呢?(代码片段2的情况)

    这样的情况下,所有的监听器都只有弱引用,导致监听器会很快被垃圾回收器回收,即便是B类的对象存活(first)的情况下!所以这种方式要求监听器在A类外部必须还有个引用,并且该引用的生命周期与B一致(B自己或者B的属性)。

    综合看来,还是方案1比较可靠,虽然要考手动操作。按照方案1的重构:

     

    public class LapsedListenerTest {
    
        public static class A{
            private List listeners = new CopyOnWriteArrayList();
            private String name;
            public A(String name) {
                this.name = name;
            }
            public void addListener(PropertyChangeListener listener) {
                listeners.add(listener);
            }
            public void removeListener(PropertyChangeListener listener) {
                listeners.remove(listener);
            }
            private void fire(String property,Object oldValue,Object newValue) {
                for(PropertyChangeListener listener:listeners) {
                        listener.propertyChange(new PropertyChangeEvent(this, property, oldValue, newValue));
                }
            }
            public void setName(String name) {
                String oldName = this.name;
                this.name = name;
                fire("name", oldName, name);
            }
        }
    
        public static class B implements PropertyChangeListener{
            private int id;
            private A a;
            //占用内存
            private byte[] bt= new byte[1024*1024];
            public B(A a,int id) {
                this.id = id;
                this.a = a;
                this.a.addListener(this);
            }
    
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("on B-" B.this.id "(" evt.getPropertyName() "," evt.getOldValue() "," evt.getNewValue() ")");
            }
    
            public void dispose() {
                this.a.removeListener(this);
            }
    
        }
    
        /**
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            A a = new A("A");
            int j = 0;
            while(true) {
                B b = new B(a,j);
                //do some thing with b
                a.setName("A" j);
                b.dispose();
                j  ;
                System.gc();
                Thread.sleep(20);
            }
        }
    
    }
    

     

     

    代码片段4

     

    本方案的缺点就是必须显式调用下b.dispose()进行销毁。

    不知道还有没有其他的更好的解决方案?

     

  • linux kde 声音问题

    升级了一下kde,然后系统声音就时好时坏,老出现 “phonon 回放设备不工作 “的提示,很是郁闷。

    查了下网上,说是要设置下 多媒体的回放设备

    但点击 系统设置->多媒体(System Setting->Multimedia),设置窗口立刻崩溃(crash).

    显示错误: kcm_phonon.so: undefined symbol: _ZNK6Phonon12GlobalConfig19hideAdvancedDevicesEv

    amarok不能播放音乐。

    网上查了一下,应该是Phonon的问题

    Phonon是什么?Qt从4.4版本开始提供的一套多媒体框架,提供多媒体回放的功能。(phonon的图解说明:http://c-home.appspot.com/2009/03/2/phonon_pic.html

    检查了下系统 Phonon  *的版本,发现 Phonon的版本与kde不兼容。升级了下phonon,这个问题就不存在了。
    *

    如果声音还有问题,根据下面这篇文档设置下ALSA:

    http://wiki.archlinux.org/index.php/Advanced_Linux_Sound_Architecture

    最后还有一个问题:flash没有声音,无论用什么浏览器。

    搜索了一下,是PulseAudio的问题。

    PulseAudio is a sound server for POSIX and Win32 systems

    参考下面两篇文档的设置:

    http://developer.novell.com/wiki/index.php/Feisty/HOWTO:_PulseAudio

    http://ubuntuforums.org/showthread.php?t=789578

  • 终于在网上建了个家

    记得我刚开始学网页就是一个简单的想法,给自己弄个个人站。

    于是开始学习
    html,css,photoshop,dreamweaver,javascript,flash。然后又知道用静态html做出来的
    网站很难维护,最好是用程序做成动态的。于是又学习asp,php,数据库,java,linux。结果这一下误入歧途,回不来了。法律专业也不搞了,转行写程序了。几年来给别人也做了好些网站程序,但自己最初的那个想法还没实现。本来是想抽空自己写个简单的个人博客用的,但这个想法一直处于构想当中。最
    后还是找个现成的单用户blog程序吧。既然喜欢java,那也应该找个java的。找来找去,就找到了这个blog程序—pebble,官方网站
    – 。咱买不起房子,但在网上给自己搭个家还行吧。

    pebble的数据采用xml方式储存,没有用数据库,搭建方便,功能也不错。

    对中文支持完全没问题。刚搭建之后,发现查询中文tag的时候会有乱码问题,它的url路径里出现中文了:http://jolestar.com/tags/开源项目/.但后来才发现那是我的mod_jk的配置问题。应该加上

    JkOptions  ForwardURICompatUnparsed  ForwardDirectories
    

    这两项。第一项是让apache不要解码,把url提交给tomcat处理。第二项是让apache把目录直接提交给tomcat处理。

    这样就没问题了。

    好了,开始转我的博客吧。但刚一转就发现,编辑器少个功能:插入代码。pebble默认是用FCKEditor编辑器,弄了一下,发现FCKEditor的插件体系不好,网上找到的FCKEditor的代码插入插件都不太好用,很多是直接把代码格式化成html的格式高亮。这种方式不好,因为这样格式化,用户复制代码的时候会很不方便,会复制上冗余的东西,以后编辑也不方便。好的方式就是用js动态实现高亮,而不是直接用html格式化。看了一下javaeye的代码插入功能,觉得不错。javaeye的编辑器用的是tiny_mce,于是就把博客的编辑器替换成了tiny_mce,又把javaeye的tiny_mce的代码插入插件搬了过来。嘿,还挺好用。在此感谢javaeye。

    代码插件有了,然后就是代码高亮。javaeye用的是SyntaxHighlighter, http://alexgorbatchev.com ,但它好像用的是旧版本,或者是它的修改版本,不太好用。于是下载了最新的SyntaxHighlighter,实验了一下,嘿,效果不错。但又发现一个问题。新版本的SyntaxHighlighter把每种语言的高亮js都放在单独文件里,必须提前把需要用到的需要高亮的语言的js都加载进来。但页面模板里没法知道博客的内容会插入那些语言的代码,如果把所有的语言的js都加载进来,会严重影响页面加载速度。于是又写了个动态加载语言js的脚本。

    var syntaxhighlighter_url = "/scripts/syntaxhighlighter/";
      var brushLibs = {js:"JScript",jscript:"JScript",javascript:"JScript",
    		  bash:"Bash",shell:"Bash",css:"Css",actionscript3:"AS3",as3:"AS3",cpp:"Cpp",c:"Cpp",
    		  csharp:"CSharp",groovy:"Groovy",java:"Java",javafx:"JavaFX",jfx:"JavaFX",
    		  perl:"Perl",pl:"Perl",php:"Php",text:"Plain",plain:"Plain",
                     py:"Python",python:"Python",
    		  ruby:"Ruby",ror:"Ruby",rails:"Ruby",scala:"Scala",sql:"Sql",xml:"Xml",html:"Xml",
                     xhtml:"Xml",xslt:"Xml"
    		  };
      function loadBrushLibs(){
    	  var pres = document.getElementsByTagName("pre");
    	  for(var i=0;i
    
  • 第一次烫头发,谷歌搬家了,脑袋烧坏了,西游记还可以这样读等若干流水帐

    上周日,在女朋友的逼迫下去理发。结果理发师说我这脑袋,上窄下宽,咋剪都不好看,要烫一下,让头发蓬起来,就均衡了,于是被忽悠了。由于是第一次烫头发,当那个锅盘一样的东西在脑袋上面转悠并且让脑袋发热的时候,我就担心这东西会不会把我的脑袋给烧坏了?

    周一,下班时分,脑袋有点痛。真的烧坏了?

    周二,上班路上就收到短信说谷歌搬家了!上网一看,门户网站的评论和社区上的言论对比明显,两极分化严重,脑袋有点痛,没想明白咋回事,突然灵光一动,我用屁股想明白了。

    有人拿出“规则说”批评谷歌:各国的法律不同,规则不同,进入不同的国家就要遵守不同的法律和规则。似乎阮次山大叔批评谷歌的时候就持这一论调。其实遵守规则这说法我同意,但前提是规则必须是规则。什么是规则?这个概念要定义下估计也能写篇论文,在这里就类推一下,拿交通规则来比较下,我们立刻可以简单的概括出规则的两个基本特征:

    1. 规则要有明文公示。没有一个国家会把交通规则藏起来不让大家知道吧?
    2. 规则要有一定的稳定性。没有一个国家会三天两头改交通规则吧?要是早上弄个绿灯行红灯停,下午就该成红灯行绿灯停,谁受的了?用着两个基本特征来检验下中国的网络审查规则,有公示么?哪个官方网站公布过滤规则?或者叫敏感词汇?有稳定性么?这个更不用说了。

    周三,凌晨时分,发烧严重,吃了退烧药。早上起来,不烧了,但脑袋还是痛,请了假去医院。挂号的时候问挂哪个科?我说脑袋痛,于是就到了神经内科。大夫问明情况后说我应该去发热门诊。我说我不发热了,就是头痛,并且发热不要紧,别让我头疼就行。大夫说头痛是发热引起了,要治头疼就得先治发热。于是开药回来吃。

    周四,发烧到39.5,头痛欲裂。再去医院,这次到发热门诊。还是开药回来吃。

    周五,情况好转。

    周六,看了篇帖子。算是对西游记的一种另类解读吧。作者用逻辑推理的方式,论证了唐僧其实是那个杀了他爹的强盗的儿子,唐僧后来报仇,其实结果是弑父。唐僧肉其实早已经被吃过,金蝉子转世十次其实是把自己的肉奉献给众菩萨罗汉吃了。红孩儿是太上老君和铁扇公主的私生子。等等等等。虽然解读的确实也有点另类,但作者的推理逻辑还是比较严密的,只不过他的大前提条件是西游记这本书的作者写这本书的时候是通过严密的逻辑推理写出来的,许多表面上不符合逻辑的内容其实是作者有意留下的破绽,让后人来解密。不过我觉得作者这个大前提是有问题的,因为中国自古以来对逻辑这东西不是很感兴趣。战国的时候,还有几个对逻辑感兴趣的(名家),其实也只能算是诡辩术,搞了几个著名的诡辩悖论题目:”坚白论”,”白马非马论”等等。结果中国的“君子”认为这种用偷换概念或者混淆法等语言技巧进行的辩论会让人迷惑,无益于”大道”,于是后世基本也没有了传人。其实这种诡辩是逻辑学之母,如古希腊的智者学派也喜欢弄诡辩。虽然智者学派后来也消亡了,但逻辑学在西方成长起来了。似乎扯远了,我是想说明,在没有逻辑学文化传统的中国文化里要孕育出一本非常有逻辑性的小说似乎是不可能的,也就是作者的大前提似乎错了。

    总结一下,先后关系和因果关系是不同的。这个道理虽然很浅显,但似乎不易懂。中国查封互联网色情内容的主要一个理由就是色情内容会引导青少年进行性犯罪。经常看到的报道就是抓住了一个强奸犯,然后问他是不是看过色情内容,他回答是,然后就推理出色情网站的危害。不过我觉得应该问他吃过饭了没,他肯定说吃过了,保暖思淫欲么。那是不是也应该禁饭?

  • eclipse在新版本GTK 上的一个bug–鼠标点击按钮无响应的解决办法

    昨天手闲,升级了一下系统,结果今天写程序,发现eclipse的很多窗口上的按钮点击都没反应了,什么都新建不了。以为是啥插件装错了,启动了一个新下载的eclipse,也不行。

    应该是系统问题了。

    搜索了一下,果然是新版本的GTK 和 eclipse 冲突。

    我用的系统是opensuse11.2

    GTK是 2.18.1-3.5

     

    opensuse社区关于这个问题的讨论:

    Eclipse 的bug报告:

    Novell的bug报告:

    一个临时的解决办法是:

    运行eclipse前设置一个系统变量:

     

    GDK_NATIVE_WINDOWS=true
    

     

     

  • 如何才能根除色情网站?

       弄了个服务器,就放了个技术博客。还三天两头被审核。江西电信把全省的机房都给屏蔽了,说是要查封色情网站!  看来色情网站一天不死,做互联网的就 一天不能安生。所以我也动动脑筋,发扬一下人民当家作主的精神,给我们伟大的公仆们献言献策.

    人毕竟是个动物,是动物它就有繁衍的本能,然后到了成熟期它就会发情。要根除色情网站,我觉得非要从根本入手。就如同我家养的那只公猫,才8个月大,它就发情了,整天叫个不停,还到处撒尿,闹的人不能安生。于是,我是先威逼:” 再叫我找人做了你!”.结果它就在我发火那一下安静一会。没办法,我就利诱,”别叫了我给你喂好吃的!”  结果它就在吃东西那会不叫。怎么办?我带它去医院,阉了它,从此世界清静了。

    以此类推,要根除色情网站,我觉得有个绝妙的办法,向领导们献策。从现在开始,规定所有的医院的妇产科,生的男孩,都给阉掉,生的女孩,都给缝上。以后繁殖都通过胚胎人工培育。这样我觉得色情网站就没有了用户,自然倒闭,同时解决了了青少年早恋,早孕的问题,还整个斩断了色情业。从此以后,中国的青少年都把心思花费在学习上,中国的成年人都把心思都花费在工作上,不出一代,中国绝对大国崛起。

    妙哉! 妙哉!

subscribe via RSS