最近线上遇到几个小问题,排查代码发现基本都是些细节问题,作些总结提示你们不要掉到坑中。前端
1、fastjson的序列化SerializerFeature使用注意 java
咱们都知道Integer、Double、Boolean等包装类型的字段默认值是null。若是不对这些字段设置值,那么在反序列化时获得的相应的值也应该是null。mysql
一、本次服务接口升级后致使调用方业务逻辑判断失败,好比Integer type 在升级以前是返回的null,而升级后返回了数字0。究其缘由发现同事在序列化时设置了SerializerFeature.WriteNullNumberAsZero,而致使Number类型(Boolean,Integer,Float,Double等)都转成了0。sql
二、null属性不显示数据库
咱们先来看一段代码json
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,怎么就凭空消失了哪?这里不讲述缘由,只说明解决方案。vim
解决方案:使用fastjson的SerializerFeature序列化属性,即JSONObject.toJSONString(Object object, SerializerFeature... features)windows
所以上面的代码就能够改写成后端
String str = JSONObject.toJSONString(map,SerializerFeature.WriteMapNullValue);
咱们经常使用的SerializerFeature:数组
总结:
一、服务接口的测试用例覆盖率仍是过低
二、对于通用处理工具,在作修改时对工具类的来龙去脉要清楚
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.remove(index)
list.subList(int fromIndex, int toIndex).clear();
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)正常
3、illegal character: \65279
问题产生的操做过程,同事使用SVN提交java文件,发现有冲突使用UltraEdit进行了修改,从新编译时却报了异常
java:[1,0] illegal character: \65279
解决方法
将文件从新保存成UTF-8 无BOM便可。
具体缘由参看高人的解释
http://blog.csdn.net/shixing_11/article/details/6976900
4、Java线程池任务执行完毕后线程回收的问题
咱们知道ThreadPoolExecutor解决了两个重要的问题:
一、因为减小了每一个任务调用的开销,它们一般能够在执行大量异步任务时提供加强的性能
二、还能够提供绑定和管理资源(包括执行任务集时使用的线程)的方法。
当使用java中的ThreadPoolExecutor,给咱们的工做带来方便的同时,若是不当使用一样也带来巨大潜在危险。
最近在review代码时,发现线程池中的全部任务执行完毕后,线程并无被销毁。咱们知道初始化ThreadPoolExecutor会有构造参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
咱们看下TreadPoolExecutor是怎样工做的
注意
一、核心线程即core线程,只有当前线程数小于等于corePoolSize时,这时的线程才叫核心线程。
二、在新任务被提交时,若是运行的core线程少于corePoolSize,才建立新core线程。并非一开始就建立corePoolSize个core线程。
三、若是运行的线程多于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)。
关键函数
按过去执行已提交任务的顺序发起一个有序的关闭,可是不接受新任务。若是已经关闭,则调用没有其余做用。
抛出:
SecurityException - 若是安全管理器存在而且关闭此 ExecutorService 可能操做某些不容许调用者修改的线程(由于它没有 RuntimePermission("modifyThread")),或者安全管理器的 checkAccess 方法拒绝访问。
尝试中止全部的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在今后方法返回的任务队列中排空(移除)这些任务。
并不保证可以中止正在处理的活动执行任务,可是会尽力尝试。 此实现经过 Thread.interrupt() 取消任务,因此没法响应中断的任何任务可能永远没法终止。
返回:
从未开始执行的任务的列表。
抛出:
SecurityException - 若是安全管理器存在而且关闭此 ExecutorService
可能操做某些不容许调用者修改的线程(由于它没有 RuntimePermission("modifyThread")),
或者安全管理器的 checkAccess 方法拒绝访问。
启动全部核心线程,使其处于等待工做的空闲状态。仅当执行新任务时,此操做才重写默认的启动核心线程策略。
返回:
已启动的线程数
若是此池容许核心线程超时和终止,若是在 keepAlive 时间内没有任务到达,新任务到达时正在替换(若是须要),则返回 true。当返回 true 时,适用于非核心线程的相同的保持活动策略也一样适用于核心线程。当返回 false(默认值)时,因为没有传入任务,核心线程不会终止。
返回:
若是容许核心线程超时,则返回 true;不然返回 false
若是在保持活动时间内没有任务到达,新任务到达时正在替换(若是须要),则设置控制核心线程是超时仍是终止的策略。当为 false(默认值)时,因为没有传入任务,核心线程将永远不会停止。当为 true 时,适用于非核心线程的相同的保持活动策略也一样适用于核心线程。为了不连续线程替换,保持活动时间在设置为 true 时必须大于 0。一般应该在主动使用该池前调用此方法。
参数:
value - 若是应该超时,则为 true;不然为 false
抛出:
IllegalArgumentException - 若是 value 为 true 而且当前保持活动时间不大于 0。
咱们了解了ThreadPoolExecutor后,确保线程的回收就能够经过如下方式
// 在allowCoreThreadTimeOut设置为true时,ThreadPoolExecutor的keepAliveTime参数必须大于0。 executor.allowCoreThreadTimeOut(true);
// 在任务执行完后,调用shutdown方法,将线程池中的空闲线程回收 executor.shutdown();
5、Eclipse下maven项目自动打war包丢失jar包问题解决方法
因为本地没有maven仓库和强大的“墙”,使用maven命令clean package却打包失败。没有办法使用了Eclipse最原始的Export命令,打好包后发现依赖的jar都没有打进去,真是一波三折呀。现说下完整的打包过程和解决方法:
一、选择相应的profile,没有配置略过
二、.project文件
检查<buildSpec>节点中是否包含以下节点
<buildCommand> <name> org.eclipse.m2e.core.maven2Builder </name> <arguments></arguments> </buildCommand>
检查<natures>节点中是否包含以下节点
<nature>org.eclipse.m2e.core.maven2Nature</nature>
三、.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工程,再打包就能够正常了。
6、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,服务一切正常。
小结:重要的事情说一遍,检查一遍。
7、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后才删除临时文件。
每次启动检索式否有临时文件, 有则询问如何处理,就会出现如上情景。
解决方法:
一、显示隐藏文件 ll -a或ls -a
二、删除隐藏文件 rm -f *.swp
8、20880端口被占用
问题描述:
在部署线上服务时,出现20880端口被占用而没法启动。
问题分析:
20880端口被该服务器上的客户端随机选取源端口给占用掉了。
解决方案:
一、使用net.ipv4.ip_local_port_range参数,规划出一段端口段预留做为服务的端口,这种方法是能够解决当前问题,可是会有个问题,端口使用量减小了,当服务器须要消耗大量的端口号的话,好比反代服务器,就存在瓶颈了。
二、将服务监听的端口以逗号分隔所有添加到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
因为本人经验有限,文章中不免会有错误,请浏览文章的您指正或有不一样的观点共同探讨!