今天突然发现几个月前写的代码中的一个线程问题.在这里做个笔记:
因为在struts1中在整个web生命周期中,web服务器只为每个action产生一个实例,所以就会产生线程问题,就必须要编写线程安全的代码.
需求是这样,简单的来说我需要保存一个学生的一些信息,但之前我需要加载这个学生相关的一些信息,信息量很大,然后在这些信息中筛选出需要的保存起来.关键在于这个加载的过程,因为信息量比较大所以我想怎么能够尽量提高它的效率.
我最开始的想法:能够在用户进入保存页面的时候先预先把学生信息加载好,然后用户在保存的时候就能够只做保存的事情,也就是把本来该一个动作做的事情分开城两个动作来做.这样来提升用户的体验.
service层中的代码:
public class LoadStudentWccDataService { private static LoadStudentWccDataService instance; private static String studKey; private Map modelMap =new HashMap(); private LoadStudentWccDataService(String studKey, UserInfo userInfo) throws ApplicationException { loadWccDataByStudentKey(studKey, userInfo); } public static LoadStudentWccDataService getInstance(String studentKey, UserInfo userInfo) throws ApplicationException { if (instance ==null || !studentKey.equals(studKey)) { studKey=studentKey; instance= new LoadStudentWccDataService(studentKey, userInfo); } return instance; } public Object getModelByModelType(String modelType) { return modelMap.get(modelType); } private void loadWccDataByStudentKey(String StudKey, UserInfo userInfo) throws ApplicationException { ..............//加载学生信息到map中 }
这样的话不会每次加载学生信息的时候都新建一个对象,同一个student就不用加载了
每次进保存页面的时候执行getInstance加载信息,保存的时候就执行getModelByModelType,取出加载的性息.
我满心欢喜,以为自己在达到需求的同时又兼顾了效率.但是现在看来却有重大的问题.
我从开始考虑到写出的代码都没有考虑多线程的情况
一个学生进入了保存页面,执行了getInstance,在保存之前另一个学生又进来了,重新执行了getInstance,这样就把上个学生的信息覆盖了,最后两个学生保存的都是后一个学生的信息.
怎么改呢?
首先想到的是线程同步,我把这个类所有的方法都加上synchronized,这个在同一个时间只能有一个学生调用这个类的方法,其他都得排队(这里要把所有的方法都加synchronized,因为锁是针对class的,一个线程调用了这个类中的一个同步方法,那么其他线程在同一时间不能调用任何一个同步方法).但这也没对.
因为我把加载信息和保存信息在serviece中放成两个方法,这样就算同步了方法也会造成问题.
怎么办,1)是把加载信息和保存信息在service中放成一个方法,这样就可以用同步来解决问题.
2)是每次getInstance的时候都生成了一个新的对象,也不会有问题
最终我选择了第2种,没错,效率比较低,不过在这种情况下我也实在想不出两全其美的办法,如果谁有更好的方法希望提出来.
总结,在struts1中要特别注意线程问题,也就是你编写的类最好是非可变类,所谓非可变类就是你的方法中不要有各个方法之间共享的变量,如上面的studKey.即使有也要加上final让它成为只读的.
如果不是则很容易产生线程问题