关于Collection中的安全

来源:互联网 发布:jquery定义一个数组 编辑:程序博客网 时间:2024/04/30 07:59

 

    以前听老师在讲Collection集合框架的时候,说到了Iterator来单线程修改和删除集合里面的数据,意思是Iterator来操作删除和修改集合里面的数据,感觉也没什么关系啊,但是科学的态度永远的严谨的,就在我兴奋地时候,问题突如其来:
问题场景: 在Session在事务后立即关闭和Lazy为False,另外查询缓存未显式打开的情况下,并且由于hibernate list()在实际情况下是不开启查询缓存的,Iterator开启,现在我在此情况下执行一次性查询加载外键对象数据,那么要显式的迭代并调用外键中的函数,以便生成相应的数据,但是就是在这里出现了问题,给我抛了一个大大的ConcurrentModificationException,以前我也用过显式调用来加载外键对象数据,但是没有遇到过这种问题,到底是什么导致的呢?我现在就这个问题来分析
//以前场景
List<Sightholders> arraySightholders = null;
arraySightholders = session.createQuery(
      "from Sightholders order by sightholdersId asc")
      .setFirstResult(pageSize * (pageNo - 1)).setMaxResults(
        pageSize).list();
    System.out.println("长度" + arraySightholders.size());
    for (Iterator<Sightholders> c = arraySightholders.iterator(); c
      .hasNext();) {
     c.next().getStafferId();
    }


//当前场景  
List sigsCheckInfo = null;
sigsCheckInfo = session.createQuery(sigsHQL)
          .setParameter(0, dateStr)
          .setParameter(1, sigsId)
          .setFirstResult(pageSize * (pageNo - 1))
          .setMaxResults(pageSize)
          .list();
   boolean isScof = false;
   boolean isRcof = false;
  
   for(Iterator iter = sigsCheckInfo.iterator() ;iter.hasNext();){
    Object obj = iter.next();
    
    if((obj instanceof SupplierCheckOutForm) || isScof){
     SupplierCheckOutForm scof = (SupplierCheckOutForm)obj;
     scof.getSupplierId().getSightholdersName();
     scof.getFpreparer().getName();
     isScof= true;
    
    }else if(obj instanceof ReceivingCheckOutForm || isRcof){
     ReceivingCheckOutForm rcof = (ReceivingCheckOutForm)obj;
     rcof.getReceivingId().getSightholdersName();
     rcof.getFpreparer().getName();
     isRcof= true;
    }
    
   }


我们知道在Iterator操作的时候是单线程的,这样是确保在多客户使用同一个数据集合的时候避免出现线程不同步的情况,这样对于性能还是数据安全和数据完整来说都是毁灭性的打击,那么我分析了这两段代码本质上没什么不同,但是我们可以看到一个明显的区别,那就是泛型的引入,在场景一中是指定了泛型的,而场景二没有,这样就导致了我在场景二需要存入可能是不同的对象,当我们以Object类型来读取出数据库中的数据后,并没有指定相应的数据类型,从而导致在Iterator操作的时候出现了修改数据类型的一种操作,从Object脱离到可能两种的对象,所以这个时候出现了修改,这个时候就会威胁到Iterator 本质的安全,所以给了我一个大大的 ConcurrentModificationException
我们来分析下这个异常:java.util.ConcurrentModificationException
导致抛出此异常的原因是:对集合类进行迭代的时候对里面的内容进行了修改,即不能用他们的Api来修改集合里面的内容,这样会导致出现这样的问题
下面我对场景二代码做了如下修改,一切正常:
    if((obj instanceof SupplierCheckOutForm) || isScof){
     SupplierCheckOutForm scof = (SupplierCheckOutForm)obj;
     scof.getSupplierId().getSightholdersName();
     scof.getFpreparer().getName();
     sigsCheckInfo.remove(obj);
     sigsCheckInfo.add(scof);

    
//     iter.remove();
     isScof= true;
    
    }else if(obj instanceof ReceivingCheckOutForm || isRcof){
     ReceivingCheckOutForm rcof = (ReceivingCheckOutForm)obj;
     rcof.getReceivingId().getSightholdersName();
     rcof.getFpreparer().getName();
     sigsCheckInfo.remove(obj);
     sigsCheckInfo.add(rcof);

//     iter.remove();
     isRcof= true;
    }

注意在红色类的对象RCOF 和SCOF中,需要添加HasCode和Equals方法,否则会导致删除不了
也可以使用如下的修改:
   for(Iterator iter = sigsCheckInfo2.iterator() ;iter.hasNext();){
    Object obj = iter.next();
    
    if((obj instanceof SupplierCheckOutForm) || isScof){
     SupplierCheckOutForm scof = (SupplierCheckOutForm)obj;
     scof.getSupplierId().getSightholdersName();
     scof.getFpreparer().getName();

     sigsCheckInfo.add(scof);
     iter.remove();

     isScof= true;
    
    }else if(obj instanceof ReceivingCheckOutForm || isRcof){
     ReceivingCheckOutForm rcof = (ReceivingCheckOutForm)obj;
     rcof.getReceivingId().getSightholdersName();
     rcof.getFpreparer().getName();
     sigsCheckInfo.add(rcof);
     iter.remove();

     isRcof= true;
    }
    
   }  

在此我用了两个List来存储和转换,也相当于数据的相互转移,当然这里返回的最好是LinkedList,因为链表的操作增加删除快,然后通过Iterator的安全remove操作,对原始的Iterator对象进行抽取.
当前在另外的场景中也有出现此问题的,例如下面的这个例子:
public void setReparation(Reparation reparation) {
  for (Iterator it = this.reparations.iterator(); it.hasNext();) {
   Reparation repa = (Reparation) it.next();
   if (repa.getId() == reparation.getId()) {
    this.reparations.remove(repa);
    this.reparations.add(reparation);
   }
  }
}注意在此代码中Reparation reparation 为集合类,例如ArrayList,等,由于在Iterator中对Arraylist进行了remove操作,所以我们发现报了异常,可以采取下列方式来进行修改:

public void setReparation(Reparation reparation) {
  boolean flag = false;
  for (Iterator it = this.reparations.iterator(); it.hasNext();) { //reparations为Collection  
   Reparation repa = (Reparation) it.next();
   if (repa.getId() == reparation.getId()) {
    it.remove();
    flag = true;
    break;
   }
  }
  if (flag) {
   this.reparations.add(reparation);
  }
}


综上所述:我们在编程的时候尤其是在对Iterator中的数据进行对集合类中数据进行删除操作的时候可以再Iterator中进行,但是关于修改操作,我们要换中方式在 Iterator中变相进行修改,养成良好的多线程变成习惯,将代码危险防范于未然.
原创粉丝点击