最近在工作的过程中,遇到了不少奇怪自己或者同事的Bug,都是一些出乎意料的,不太容易发现的,记录一下来帮助可能也遇到了这些Bug的人
Map<String, String> nameToType = new HashMap<>();
nameToType.put( "testName", 123 ); // java: 不兼容的类型: int无法转换为java.lang.String
上面的代码,我们很容易看出来,无法通过编译,因为Map的value需要的是一个String,但我们传的是一个int。但我只要稍微改一下:
package generic;
import java.util.HashMap;
import java.util.Map;
public class RecContext<T>{
private Map<String, String> nameToType = new HashMap<>();
public Map<String, String> getNameToType(){
return nameToType;
}
public void setNameToType( Map<String, String> nameToType ){
this.nameToType = nameToType;
}
public static void main( String[] args ){
RecContext recContextRaw = new RecContext();
recContextRaw.getNameToType().put( "testName", 123 );
}
}
同样是一个value要求为String的Map, 放到一个对象里面就可以通过编译了
不过这不是一个普通的对象,这个Class本身带泛型,但我们在使用的时候,没有指定这个泛型,也就是IDEA中常常报的错,说你在Raw Use这个类型
也正是因为Raw Use了这个类,所以导致它的泛型属性也被类型擦除了,具体可以看StackOverflow-What is a raw type and why shouldn't we use it?
这篇文章里面是这么说的
In simpler terms, when a raw type is used, the constructors, instance methods and non-static fields are also erased
简单地讲,当使用了原始类型,构造器、实例方法和非静态的字段都会被擦除
我们有一个老的项目里面有不少这样的Raw use,也正好有另外一个同事把一个其他的类型插入了这个Map,于是就报了一个类型转换错误,同事们都很震惊,认为这个Map不是有泛型吗,怎么可能插入别的类型,一番排查,才发现是这个问题
线上代码中有这样一个逻辑,想从帖子列表中筛选出一部分帖子然后从原列表中删除,其代码逻辑如下:
import java.util.ArrayList;
import java.util.List;
public class ListDelTest{
public static void main( String[] args ){
List<Integer> jobIds = new ArrayList<>();
for( int i = 0; i < 10; i++ ){
jobIds.add( i );
}
List<Integer> delIndex = new ArrayList<>();
for( int i = 0; i < jobIds.size(); i++ ){
// 线上是其他的筛选逻辑,在这我们用偶数代替
if( jobIds.get( i ) % 2 == 0 ){
delIndex.add( i );
}
}
for( int i = 0; i < jobIds.size(); i++ ){
if( delIndex.contains( i ) ){
jobIds.remove( jobIds.get( i ) );
}
}
System.out.println( jobIds );
// [1, 2, 4, 5, 7, 8]
}
}
可以看到输出结果中,并不符合我们的预期,我们希望的是把所有的偶数删除,结果中不但有偶数,而且一些奇数也不见了
这个其实很容易理解,因为我们记得位置是0,2,4,6,8
原始数据是0,1,2,3,4,5,6,7,8,9
当我们删除了0时,数据变成了1,2,3,4,5,6,7,8,9
这时候我们再去删除index是2的值,结果就把3这个值给删除了
ConcurrentModificationException
异常 int i = 0;
Iterator<Integer> iterator = jobIds.iterator();
while( iterator.hasNext() ){
iterator.next();
if( delIndex.contains( i ) ){
iterator.remove();
}
i++;
}
System.out.println( jobIds );
// [1, 3, 5, 7, 9]
for( int i = jobIds.size() - 1; i > -1; i-- ){
if( delIndex.contains( i ) ){
jobIds.remove( jobIds.get( i ) );
}
}
线上同事上线了一个新的过滤器,我们的过滤器是并发执行的,比如,帖子敏感词过滤,会将帖子分成10份,用10个线程分别执行,执行完了就把结果放到一个公共的map中
很明显,这个map是有线程安全问题的,因为会有多个线程同时去put,然而,因为没考虑到,同事使用了普通的HashMap
线上的现象就是,每过一段时间,某个机器的CPU使用率就到了90%以上,需要重启
按照我们的理解,就算是有并发问题,怎么会使CPU使用变高呢
我们都背过,在Java1.7中的HashMap会因为并发插入产生死循,1.8使用尾插法代替头插法解决了死循环
可我们用的是Java1.8,看起来好像还是有死循环的问题
具体原因我就不仔细分析了,是在链表转换树或者对树进行操作的时候会出现线程安全的问题
[1] StackOverflow-What is a raw type and why shouldn't we use it?