为JAVA添加信号/槽支持(二)

    这次我们将解决上一篇中提到的3个问题。

    首先纠正一下第一个问题的说法,上一篇中提到的第一个问题应该是“当前系统中注册的对象的引用只剩下信号/槽处理器中的列表时,应该将此对象释放,以便JVM能够回收资源”。

第一个问题的解决

    JAVA垃圾加收机制是将当前虚拟机中没有被引用的对象销毁,释放内存,回收资源。在我们的信号/槽处理器中,只要对象曾经注册过,那么列表就持有对象的一个引用,导致JVM中只有信号/槽持有对象的引用时也不会回收对象,而当信号/槽处理器外部没有这个对象的引用时,此对象对于信号/槽处理器已经没有意义,也就是说这个对象在当前系统中已经没有实际的用处,所以需要将这个对象释放。那么我们如何知道,信号/槽处理器之外已经没有对象的引用呢?好在JAVA为我们提供了四种种引用方式:

1、 强引用                           

String s = new String("test");
String s1 = s;

     这种我们编程中最常用的方式即是建立了对象的强引用,JVM在回收垃圾时将一个在当前虚拟机中没有强引用的对象进行回收资源(JVM垃圾机制实际上通过引用到对象的方式,即一个对象通过引用是不可达的,则为回收对象)。

2、软引用

CharBuffer cb = CharBuffer.allocate(1024 * 1024 * 1);
SoftReference<CharBuffer> s = new SoftReference<CharBuffer>(cb);
System.out.println(s.get());
cb = null;
CharBuffer cbt = CharBuffer.allocate(1024 * 1024 * 3);
System.gc();
System.out.println(s.get());

     以软引用方式持有一个对象引用时,JVM进行垃圾回收时将判断当前虚拟机内存是否足够,当内存不够时将回收这个对象,当内存足够时不回收。上面的程序中,第三行将输出CharBuffer的内容,而第七行将输出"null"。执行上面程序,JAVA虚拟机设置最大最小内存为10M。当虚拟机内存设置较大时,第7行返回对象。

3、弱引用

WeakReference<String> s = new WeakReference<String>(new String("dfdf"));
System.out.println(s.get());

    以弱引用方式持有一个对象引用时,JVM进行垃圾回收时,若此对象没有强引用,则将回收对象资源。上面的程序有非常大的可能输出为"null",因为对象已经被回收。在上面程序中间加上System.gc();手动执行垃圾回收后,输出的结果一定是"null"。

4、幽灵引用   

ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
String st = new String("dfdf");
PhantomReference<String> s = new PhantomReference<String>(st, referenceQueue);
System.out.println(s.get());
st = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
Object so = referenceQueue.poll();
System.out.println(so);

    创建幽灵引用时必须指定引用队列,当幽灵引用指向的对象被回收时,对象将被加入引用队列(立即或者有个延时,在我所做的实验中还没有遇到立即加入队列的,总是会有个延时)。在上面的程序中,第四行将输出"null",幽灵引用的get()函数总是返回null;第九行将返回对象,如果将第七行去掉,第九行将返回null。关于幽灵引用的作用请参考这里

    对于我们第一个问题的解决,正是使用上面四种引用中的弱引用。修改类ConnectInfo,将强引用修改为弱引用:

class ConnectInfo {

   WeakReference<Object> signalObject = null;        // 发送信号对象
    WeakReference<Object> slotObject = null;          // 接受信号对象
   String signal = null;              // 信号
    String slot = null;                // 信号处理函数
    Class<?>[] params = null;          // 传递的参数类型数组

   public Object getSignalObject() {
        return signalObject.get();
    }

    public void setSignalObject(Object signalObject) {
        this.signalObject = new WeakReference(signalObject);
    }

    public Object getSlotObject() {
        return slotObject.get();
    }

    public void setSlotObject(Object slotObject) {
        this.slotObject = new WeakReference(slotObject);
    }

   public String getSignal() {
        return signal;
    }

    public void setSignal(String signal) {
        this.signal = signal;
    }

    public String getSlot() {
        return slot;
    }

    public void setSlot(String slots) {
        this.slot = slots;
    }

    public Class<?>[] getParams() {
        return params;
    }

    public void setParams(Class<?>[] params) {
        this.params = params;
    }
}

当对象被回收后getSlotObject()、getSignalObject()函数将返回null。一点说明:在WeakHashMap类中,使用一个引用队列对弱引用进行初始化,key使用弱引用。然后检查引用队列是否已有被回收对象(JVM回收对象时,会将对象信息放到引用队列中),以此删除已经被回收的entry。然而JVM将回收对象放入引用队列中会有延时,而信号/槽处理器需要实时去除队列中的ConnectInfo对象,以防止空指针异常,所以以getSlotObject()、getSignalObject()函数返回null为判断标准,将ConnectInfo对象从队列中删除。

public class SignalSlotHandle {

    /**
     * 同步列表,保存注册的信号/槽信息
     */
    private static List<ConnectInfo> conns = Collections.synchronizedList(new ArrayList<ConnectInfo>());
    

    /**
     * 同步函数,注册信号/槽
     *
     * @param o 信号发送对象
     * @param signal 信号
     * @param ot 信号接收对象
     * @param slot 信号处理函数,使用字符串表示的函数名
     * @param c_param 信号传递的参数类数组,必须是信号处理函数实际参数类型,且顺序必须一致
     */
    public static synchronized void connect(Object o, String signal, Object ot, String slot, Class<?>... c_param) {
        ConnectInfo ci = new ConnectInfo();
        ci.setSignalObject(o);
        ci.setSlotObject(ot);
        ci.setSignal(signal);
        ci.setSlot(slot);
        ci.setParams(c_param);
        conns.add(ci);
    }

    /**
     * 同步函数,发送信号和参数
     *
     * @param o 发送信号对象
     * @param signal 信号
     * @param args 参数对象数组,允许参数个数大于注册时的参数个数,但必须参数类型必须与注册时保持一致,且个数不能小于注册时个数
     */
    public static synchronized void emit(final Object o, final String signal, final Object... args) {
      new Thread() { // 当通过EDT调用此函数时,这步将使此立刻返回,从而使槽函数的执行脱离EDT
          @Override
          public void run() {
            execSlot(o, signal, args);
           }
        }.start();
    }

    private static void execSlot(Object o, String signal, Object... args) {
     List<ConnectInfo> ciRemove = new ArrayList<ConnectInfo>();
     final Object[] argst = args;
     for (ConnectInfo ci : conns) {
            if (ci.getSignalObject() == null || ci.getSlotObject() == null) {
                ciRemove.add(ci);
                continue;
            }
           if (ci.getSignalObject() == o) {
                if (ci.getSignal().compareTo(signal) == 0) {
                    final ConnectInfo cit = ci;
                    new Thread() {      // 为使用所有处理信号的函数能够同时执行,使用线程方式调用
                        @Override
                        public void run() {
                            // 使用反射调用信号处理函数
                            try {
                                Method method;
                                method = cit.getSlotObject().getClass().getMethod(cit.getSlot(), cit.getParams());
                                int alen = argst.length;
                                int plen = cit.getParams().length;
                                Object[] t;
                                if (alen > plen) { // 用于支持信号传递的参数个数大于实际参数个数,要求相应参数类型必须相同
                                    t = Arrays.copyOf(argst, plen);
                                } else {
                                    t = argst;
                                }
                                method.invoke(cit.getSlotObject(), t);
                            } catch (IllegalAccessException ex) {
                                Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                            } catch (IllegalArgumentException ex) {
                                Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                            } catch (InvocationTargetException ex) {
                                Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                            } catch (NoSuchMethodException ex) {
                                Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }.start();
                }
            }
        }
        for (ConnectInfo t : ciRemove) {
            conns.remove(t);
        }
   }

    public static void uiExecUpdateInEDT(final JComponent jc, final String fun, final Object... params) {
         Runnable run = new Runnable() {
            public void run() {
                System.out.println("uiExecFunInEDT in EDT: " + SwingUtilities.isEventDispatchThread());
                try {
                    Class<?>[] pclass;
                    if (params.length > 0) {
                        pclass = new Class<?>[params.length];
                        for (int i = 0; i < params.length; i++) {
                            pclass[i] = params[i].getClass();
                        }
                    } else {
                        pclass = new Class<?>[0];
                    }
                    Method method = jc.getClass().getMethod(fun, pclass);
                    method.invoke(jc, params);
                } catch (NoSuchMethodException ex) {
                    Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                } catch (SecurityException ex) {
                    Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IllegalAccessException ex) {
                    Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IllegalArgumentException ex) {
                    Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                } catch (InvocationTargetException ex) {
                    Logger.getLogger(SignalSlotHandle.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        };
//        SwingUtilities.invokeLater(run);
        new Thread(run).start();
    }
}

上面代码的execSlot函数为解决第一个问题所作的修改,uiExecUpdateInEDT函数为解决第二、三个问题添加的函数(将在第三篇中说明)。在函数execSlot中新建临时列表ciRemove用于存放将要移除的ConnectInfo对象。在循环中首先判断ConnectInfo对象的getSlotObject()或getSignalObject()函数返回值是否为null,若是则将当前对象加入移除列表,循环继续。在循环结束后,将加入移除列表ciRemove的ConnectInfo对象从信号/槽列表conns中移除。
    至此,基本解决了第一个问题中的资源占用和空指针问题。

        一个新的问题:上面注册对象的移除是在虚拟机进行垃圾回收之后,那么在虚拟机进行垃圾回收之前,就算是弱引用也始终保持着对象,这时,当外部没有强引用时,也会执行相应槽函数,如下面代码。

public class SSTest {

    public static void main(String[] args) throws InterruptedException {
        
        Object signalo = new Object();
        SSTest sloto = new SSTest();
        System.out.println("one");
        SignalSlotHandle.connect(signalo, "test", sloto, "testSlot");
        SignalSlotHandle.emit(signalo, "test");
        TimeUnit.SECONDS.sleep(1);
        sloto = null;        
    //  System.gc();
        System.out.println("two");
        SignalSlotHandle.emit(signalo, "test");
    }
    public void testSlot(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException ex) {
            Logger.getLogger(SSTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println(100);
    }
}

上面的程序将会输出两个100,这是因为尽管sloto已经赋值为null,但由于JVM没有进行垃圾回收,对象还在,所以信号槽处理器没有将此信号/槽注册信息去除;将12行的注释去掉后,将只输出一个100。在文章防范JAVA内存泄漏解决方案中提到一个反向链接(referer)计数,应该能够解决这个问题。只是没有找到相关资料,望高手赐教。





    

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fengdavid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值