持续更新线上问题及解决方案

     最近线上遇到几个小问题,排查代码发现基本都是些细节问题,做些总结提示大家不要掉到坑中。

一、fastjson的序列化SerializerFeature使用注意                                       

    我们都知道Integer、Double、Boolean等包装类型的字段默认值是null。如果不对这些字段设置值,那么在反序列化时得到的相应的值也应该是null。

1、本次服务接口升级后导致调用方业务逻辑判断失败,比如Integer type 在升级之前是返回的null,而升级后返回了数字0。究其原因发现同事在序列化时设置了SerializerFeature.WriteNullNumberAsZero,而导致Number类型(Boolean,Integer,Float,Double等)都转成了0。

2、null属性不显示

我们先来看一段代码

Map <String , Object > map = new HashMap<String , Object>();
map.put("a",1);
map.put("b","");
map.put("c",null);

String str = JSONObject.toJSONString(map);
System.out.println(str);//输出结果:{"a":1,"b":""}

属性c,怎么就凭空消失了哪?这里不讲述原因,只说明解决方案。

解决方案:使用fastjson的SerializerFeature序列化属性,即JSONObject.toJSONString(Object object, SerializerFeature... features)

因此上面的代码就可以改写成

String str = JSONObject.toJSONString(map,SerializerFeature.WriteMapNullValue);

我们常用的SerializerFeature:

  • SerializerFeature.WriteMapNullValue, //输出空置的字段
  • SerializerFeature.WriteNonStringKeyAsString,//如果key不为String 则转换为String 比如Map的key为Integer
  • SerializerFeature.WriteNullListAsEmpty,//list为null时输出[] 
  • SerializerFeature.WriteNullNumberAsZero,//number为null时输出0
  • SerializerFeature.WriteNullStringAsEmpty,//String为null时输出""
  • SerializerFeature.WriteNullBooleanAsFalse,//boolean为null时输出false
  • SerializerFeature.QuoteFieldNames,//输出key时是否使用双引号,默认为true
  • SerializerFeature.DisableCheckSpecialChar//一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false 

    总结:

    1、服务接口的测试用例覆盖率还是太低

    2、对于通用处理工具,在做修改时对工具类的前因后果要清楚

二、java.util.List.subList的陷阱                                                             

     我们一般都会使用java.util.List中有一个subList方法,返回一个以fromIndex为起始索引(包含),以toIndex为终止索引(不包含)的一部分的视图(List)。    

List<E> subList(int fromIndex, int toIndex);

    之所以说是视图,是因为实际上,返回的list是靠原来的list支持的。原来的list和返回的list做的“非结构性修改”(non-structural changes),都会影响到彼此对方。

所谓的“非结构性修改”,是指不涉及到list的大小改变的修改。相反,结构性修改,指改变了list大小的修改。

如果发生结构性修改的是返回的子list,那么原来的list的大小也会发生变化;

如果发生结构性修改的是原来的list(不包括由于返回的子list导致的改变),那么会是抛出一个ConcurrentModificationException。

  • 如何删除一个list中的某个值   
list.remove(index)
  • 如何删除一个list的某个区段
list.subList(int fromIndex, int toIndex).clear();
  • 如何修改子list视图而不影响原来的list或修改原list而不影响子list视图
List<Integer> subList2 = new ArrayList<Integer>(list2.subList(2, list2.size()));

 那么ArrayList的remove的底层是怎么做的?

      AbstractList中有一个属性modCount,这个属性是跟踪list中数据被修改的次数,任何对list的add/remove操作,都将导致modCount++。

在AbstractList中还有一个内部类Itr implements Iterator,Itr是一个list遍历的工具类。当然list.iterator()方法也是返回Itr对象,在Itr中有一个校验位属性expectedModCount;对于一个itr对象,其初始时expectedModCount=modCount。

      Iterator是list一个视图,其最终还是操作list的存储结构。在使用iterator遍历时,remove()操作,会导致modCount++(AbstractList.remove()),但是还有expectedModCount=modCount,即在iterator中remove数据,会带来expectedModCount与modCount值的同步

在Iterator遍历时,next(),remove()方法会校验expectedModCount与modCount值是否一致,如果不一致,就意味着这list数据在iterator外部被修改,此时iterator遍历将会造成ConcurrentModificationException.

      AbstractLlist不仅支持普通的iterator,还支持ListIterator(ArrayList,LinkedList均支持),ListIterator增加了遍历时双向游标能力(previous,next),增加了add方法。add方法和remove方法一样也做了expectedModCount和modCount一致性校验.

我们来看下面四个对list数据删除的代码的区别

1)
for(int i=0;i<list.size();i++){
     list.remove(i);
}

2)
for(int i=list.size()-1;i>=0;i--){
    list.remove(i);
}

3)
int size = list.size();
for(int i=size-1;i>-1;i--){
     list.remove(i);
}

4)
for(Object i : list){
   //如果list中存在多个Object互相equals时,此方法仍然有效.注意list.remove(Object)内部使用了遍历操作,并使用equals来比较对象并删除.
   list.remove(i);
}

5) 
Iterator it = list.iterator()
while(it.hasNext()){
        it.next();
        it.remove();
}

1),2),3)是最普通的遍历方式,但是在遍历并有删除操作时,似乎它们执行的结果还有些差距,根据坐标删除,

那么1)实事上只会有一半被删掉,1)中每删除一次,计算一次list.size(),但是当前i++,且前端删除会造成数组结构copy。

2)后端删除,不会造成copy,每次都是删除最后一个位置,直至结束

3)因为size没有重新计算,在删除一半数据后,抛出IndexOutOfBoundsException

4)/5)正常

  

三、illegal character: 65279                                                               

   问题产生的操作过程,同事使用SVN提交java文件,发现有冲突使用UltraEdit进行了修改,重新编译时却报了异常

java:[1,0] illegal character: 65279 

   解决方法

   将文件重新保存成UTF-8 无BOM即可。

               

具体原因参看高人的解释

http://blog.csdn.net/shixing_11/article/details/6976900

四、Java线程池任务执行完毕后线程回收的问题 

我们知道ThreadPoolExecutor解决了两个重要的问题:

1、由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能

2、还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。 

当使用java中的ThreadPoolExecutor,给我们的工作带来方便的同时,如果不当使用同样也带来巨大潜在危险。

最近在review代码时,发现线程池中的所有任务执行完毕后,线程并没有被销毁。我们知道初始化ThreadPoolExecutor会有构造参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

我们看下TreadPoolExecutor是怎样工作的

注意

      1、核心线程即core线程,只有当前线程数小于等于corePoolSize时,这时的线程才叫核心线程。

      2、在新任务被提交时,如果运行的core线程少于corePoolSize,才创建新core线程。并不是一开始就创建corePoolSize个core线程。

      3、如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。

      
按需构造
      核心线程最初只是在新任务到达时才被ThreadPoolExecutor创建和启动的,但是也可以手动调用方法 prestartCoreThread() 或 prestartAllCoreThreads()来的提前启动核心线程。
    如果构造带有非空队列的池,这时则可能希望预先启动线程。


保持活动时间

      如果线程池中当前线程数大于corePoolSize ,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
      如果后来线程池中线程变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数,如果把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS 的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。
      默认情况下,此种活动策略只在有多于corePoolSize Threads的线程时才会应用。但是只要 keepAliveTime 值非 0,也可以通过allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。
注意1:setKeepAliveTime(long, java.util.concurrent.TimeUnit)用于设置空闲线程最长的活动时间,即如果空闲时间超过设定值,就停止该线程,对该线程进行回收。
    该策略默认只对非内核线程有用(即当前线程数大于corePoolSize),可以调用allowCoreThreadTimeOut(boolean)方法将此超时策略扩大到核心线程
注意2:如果把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。

线程终止

      如果ThreadPoolExecutor在程序中没有任何引用且没有任何活动线程,线程池也不会自动 shutdown。
      如果希望确保回收线程(即使用户忘记调用 shutdown()),则必须安排未使用的线程最终终止,设置适当保持活动时间,设置 allowCoreThreadTimeOut(boolean)。

    

关键函数

  • public void shutdown()

    按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。如果已经关闭,则调用没有其他作用。
    抛出:
        SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission("modifyThread")),或者安全管理器的 checkAccess 方法拒绝访问。

  • public List<Runnable> shutdownNow()

    尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在从此方法返回的任务队列中排空(移除)这些任务。
    并不保证能够停止正在处理的活动执行任务,但是会尽力尝试。 此实现通过 Thread.interrupt() 取消任务,所以无法响应中断的任何任务可能永远无法终止。
    返回:
        从未开始执行的任务的列表。 
    抛出:
        SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 
        可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission("modifyThread")),
        或者安全管理器的 checkAccess 方法拒绝访问。

  • public int prestartAllCoreThreads()

    启动所有核心线程,使其处于等待工作的空闲状态。仅当执行新任务时,此操作才重写默认的启动核心线程策略。
    返回:
        已启动的线程数

  • public boolean allowsCoreThreadTimeOut()

    如果此池允许核心线程超时和终止,如果在 keepAlive 时间内没有任务到达,新任务到达时正在替换(如果需要),则返回 true。当返回 true 时,适用于非核心线程的相同的保持活动策略也同样适用于核心线程。当返回 false(默认值)时,由于没有传入任务,核心线程不会终止。
    返回:
        如果允许核心线程超时,则返回 true;否则返回 false

  • public void allowCoreThreadTimeOut(boolean value)

    如果在保持活动时间内没有任务到达,新任务到达时正在替换(如果需要),则设置控制核心线程是超时还是终止的策略。当为 false(默认值)时,由于没有传入任务,核心线程将永远不会中止。当为 true 时,适用于非核心线程的相同的保持活动策略也同样适用于核心线程。为了避免连续线程替换,保持活动时间在设置为 true 时必须大于 0。通常应该在主动使用该池前调用此方法。
    参数:
        value - 如果应该超时,则为 true;否则为 false 
    抛出:
        IllegalArgumentException - 如果 value 为 true 并且当前保持活动时间不大于 0。

我们了解了ThreadPoolExecutor后,确保线程的回收就可以通过以下方式

// 在allowCoreThreadTimeOut设置为true时,ThreadPoolExecutor的keepAliveTime参数必须大于0。
executor.allowCoreThreadTimeOut(true);
// 在任务执行完后,调用shutdown方法,将线程池中的空闲线程回收
executor.shutdown();

五、Eclipse下maven项目自动打war包丢失jar包问题解决方法 

 由于本地没有maven仓库和强大的“墙”,使用maven命令clean package却打包失败。没有办法使用了Eclipse最原始的Export命令,打好包后发现依赖的jar都没有打进去,真是一波三折呀。现说下完整的打包过程和解决方法:

1、选择相应的profile,没有配置略过

2、.project文件

检查<buildSpec>节点中是否包含如下节点

<buildCommand>                    
     <name>
         org.eclipse.m2e.core.maven2Builder
     </name>
     <arguments></arguments>
</buildCommand>

检查<natures>节点中是否包含如下节点

<nature>org.eclipse.m2e.core.maven2Nature</nature> 

3、.classpath文件

检查是否包含如下节点

<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
        <attributes>
            <attribute name="maven.pomderived" value="true"/>
            <attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
        </attributes>
</classpathentry>

正因为没有这个属性<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>,才导致打的包中没有jar。

上述三步骤检查完毕后,重新clean工程,再打包就可以正常了。

六、Linux系统下忽略表明大小写

坑爹的DBA将线上的一个数据库重做后,导致了某些服务抛出了大量的异常

select name,type,updateTime from VIDEO_INFO where 1=1  ExecuteData Table 'MS.VIDEO_INFO' doesn't exist

信息提示找不到表明。

我们都知道Linux是区分表明大小写,windows不区分表明大小写的。DBA重做了新的数据库后却忽略设置表明大小写。

解决方法很简单:

编辑mysql配置文件:vi /etc/my.cnf

添加:lower_case_table_names=1 一句到文件中。

重启MySQL,服务一切正常。

小结:重要的事情说一遍,检查一遍。

七、swap file "*.swp" already exists!

Linux下我们使用vi或vim对文件编辑, 当打开文件或保存文件可能会出现:

swap file "*.swp" already exists!

[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:

原因:

使用vim编辑文件实际是先copy一份临时文件并映射到内存给你编辑, 编辑的是临时文件, 当执行:w后才保存临时文件到原文件,执行:q后才删除临时文件。

每次启动检索式否有临时文件, 有则询问如何处理,就会出现如上情景。

解决方法:

1、显示隐藏文件 ll -a或ls -a

2、删除隐藏文件 rm -f *.swp

八、20880端口被占用

问题描述:
在部署线上服务时,出现20880端口被占用而无法启动。

问题分析:
20880端口被该服务器上的客户端随机选取源端口给占用掉了。

解决方案:

1、使用net.ipv4.ip_local_port_range参数,规划出一段端口段预留作为服务的端口,这种方法是可以解决当前问题,但是会有个问题,端口使用量减少了,当服务器需要消耗大量的端口号的话,比如反代服务器,就存在瓶颈了。
2、将服务监听的端口以逗号分隔全部添加到ip_local_reserved_ports中,TCP/IP协议栈从ip_local_port_range中随机选取源端口时,会排除ip_local_reserved_ports中定义的端口,因此就不会出现端口被占用了服务无法启动。、

推荐使用第二种方法

$ cat /proc/sys/net/ipv4/ip_local_port_range
32000 61000
$ cat /proc/sys/net/ipv4/ip_local_reserved_ports
8080,9148 

由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!

原文地址:https://www.cnblogs.com/exceptioneye/p/4887290.html