日常小结-关于swing某死锁问题的小结

来源:互联网 发布:淘宝库存管理系统 编辑:程序博客网 时间:2024/05/08 01:11

问题概述

最近做了一个IM一般的项目。算是写着玩不过也是个比较完整的项目,这两天遇到一个奇怪的问题就是。在接受离线的消息的时候有时候客户端方面有时候会失去响应。通常第一遍都是运行良好但是第二次运行的时候就会出问题。例如user1给user2发送信息,然后登陆user2,user2接收到信息。但是再关闭user2的客户端,然后user1重新发送一个新消息给user2。这时user2客户端会死锁。而且这部分是早先写的就最近才发现原来会出现失去响应的问题。

其实写程序就是怕这样的问题。多线程的问题,以前写的程序,测试没检测出来。

由于是第一次一般不出现问题而第二次会出现问题。我就很显然的把问题定位到了关闭客户端之后的清理工作。尝试多次后发现问题似乎不出在这里。后来多做几个测试后发现问题并非总是出现在第二次,只是之前凑巧了在第二次出问题。在user2登陆后接收离线消息的时候会有一定概率出现失去响应的问题。通常来说失去响应很多时候都是死锁问题。

定位死锁位置

我考虑到是死锁问题就开始尝试调试看看到底是哪里出现了死锁问题。首先我来简要说明下程序设计的结构问题。客户客户段的登陆界面我只用了一个类去写,也就是GUIMainwindows。剩下所有的动作都是内部类。这个类的方法只有两个地方上了锁一个是用的GUIMainwindows.this的锁。另一个就JTree。这个Jtree是用来组织用户的好友列表的,只有在关联这方面操作的时候才会使用。这就觉得比较好奇了。因为我仔细检查我程序发现并没有什么问题。而且从接受离线消息到显示只涉及到了GUIMainwindows.this一个锁,在我理解范围内应该是不会出现死锁问题的。不过凡事都要以实践为准。

我几次测试后发现并不是我本身的程序出现了错误而是处理离线消息的线程和AWTeventquet-0之间死锁了。用eclipse挂起者两个线程之后发现我自己写的程序需要Jtree的锁,但持有GUIMainwindows.this的锁,而AWTevenqueue-0则正好相反。说实话我查到这里我比较奇怪,而且也比较头疼,因为swing我只是当成一个接口来用并没有深入理解。因为显示离线信息的操作完全和好友列表完全没有关系。按照调试结果显示问题出在下列的语句。这是一个JTextArea的插入问题而且我理解中这个程序本身并不需要锁,至少不需要Jtree的锁。直到问题解决我也不明白为什么这里需要Jtree的锁。

            JTextArea display = (JTextArea)guiSession.jpanel.getComponent(1);            display.insert("\n"+sendmessage.getName()+":\n"+sendmessage.getMessage(), display.getText().length());

不过既然问题摆在这里还是需要解决的。我尝试了这里先获得Jtree的锁然后在获得GUIMainwindows.this的锁这样似乎不合理,尝试结果也依然会出现失去响应的问题。我甚至尝试了一下不加锁会不会出现问题,当然肯定会有问题但是按道理来说不应该会是死锁问题。但是尝试后发现依然会出现死锁问题。(后来发现应该是由于在处理离线消息的方法内部调用了一些子方法是需要锁的。所以依然会出现死锁问题。)

到这里我想到了之前在运行客户端的时候也会出现AWTevenqueue-0的nullpointer问题。也是有时候会出现有时候不出现。我网上搜了一下这个的原因很多,一个主要的原因是AWTeventqueue在窗口按键还没有完成初始化的时候就运行了。类似这样的问题。我回想来在user1和user2两者直接通信的时候并没有出现问题,为何离线消息就会出现这样的问题。会不会是在处理离线消息的时候实际上还没有完成一些初始化的工作。我开始在这方面找问题。

同步初始化

这里同样简要介绍下程序的基本结构,我设计这个IM的时候实际上就是在客户端这边用了两条阻塞队列,一条收消息一条发消息。在完成登陆之后会向发送队列发送一条请求好友列表的消息,然后服务器处理完成之后会返回一条好友列表消息到接受队列,然后接受到好友列表后会发送一个发送一个离线消息请求到发送队列,然后服务器同样返回到接受消息队列。这个过程按理说是没有什么问题的。而且窗口的初始化我使用的是一个静态变量的初始化,这里似乎也不应该成为问题。但是这里有一个小的问题就是在处理接受消息队列的时候是用的是一个线程池建了两个线程去处理会不会出现一个线程已经接受了离线消息,但是另一线程还有之前的任务没有完成。

所以我尝试使用一个coutndownlatch来表示是否有完成的初始化然后专门列一个新线程阻塞在这里互斥量上,然后如果完成的初始化就发送离线消息请求。其实早就想这样做了。因为从软件设计的角度上说这样似乎更合理一些。完成之后一个好消息是似乎之前偶然抛出的AWTeventqueue-0的空指针问题似乎是消失了,但是死锁问题还是没解决。(空指针的问题没有大范围测试。目前来看好像是解决了,不过过解决的糊里糊涂的。个人感觉问题应该不在这里。不过似乎不影响也就暂时先放着了。此外我回头在想这个问题的时候其实处理接受消息队列没必要用线程池,专门一个线程去做就好了,这样似乎更简介一些,因为客户端程序实际上没有这么大的并发需求,单个线程完全可以解决没必要引入复杂设计。不过程序暂时没问题也就不想改了。)

使用Lock锁

虽然略微的改善下设计但是问题还是没有解决。然后我又断点调试了几下发现似乎问题并不存在在之前写的那条语句之上,而更准确的说是一个范围之内,在整个处理离线消息的过程中有多个位置都会出现死锁。似乎问题变得更加麻烦。我在考虑如何去解决这个问题。既然不能从获得锁的顺序上解决死锁那我使用其他的锁是否可以呢?我新建了一个lock变量displaymessage,在处理离线消息的时候使用这个lock去加锁,尝试之后发现问题还是没有解决,但是问题固定在了一行语句上JPanel buildSessionwindow = buildSessionwindow(friendname);buildSessionwindow是一个我自己写的语句。但是这条语句是用synchronized 修饰词修饰的。对这些各个调用的子语句也使用displaymessage来加锁。这样一试果然是可行的。到这里的时候我想到了之前在看java并发编程实战的时候里面说到swing使用的线程封闭技术。当时每太注意这一点,按照我的理解就是swing在组织程序的时候没有用锁,而是在访问某些部件的时候仅仅依靠一个固定的线程去访问。回想到这里其实可能是类似的思路。不过实际上swing还是使用了锁。

当然我这么做其实只是暂时解决了问题。如果能理解AWTeventqueue这个线程是如何工作的那这样是最好了。我觉得我这里两个关键性的问题就是出在这里。还有一个值得考虑的问题,之前看并发方面的书的时候会介绍一些java的工具用来分析死锁的。当时觉得这些没什么用,现在才发现确实是很用有的。因为多线程的问题有时候很麻烦,麻烦不在于难而在于找不到在哪里。比如这里的问题很有意思的是在调试状态下查不出来,但是直接运行就出问题,而且有时候问题不出再某一行出问题而是在多个地方出问题。而运行状态下死锁eclipse本身是查不出来的,但是java的工具似乎是可以转储之类的提供一些信息。

0 0