分享两个你可能不知道的Java小秘密

引言

  最近LZ的工作发生了重大变化,以后博文的更新速度可能会再度回温,希望猿友们可以继续关注。

  近期LZ辞掉了项目经理的工作,不过并未离开公司,是转到了基础研发部做更基础的研发,为广大技术人员服务。这会让LZ有更多的时间去研究一些技术方面的东西,LZ打算折腾一下spring的源码,期待有一天可以成为spring代码的贡献者。

  好了,废话说到这里吧,今天先分享两个小问题的解决办法,可能你以后也会遇到的。

DBCP数据源坑爹的地方

  前几天系统出现了一个错误,比较奇葩。中文解释是“无法从套接字读取更多的数据”,原因是connection reset。首先很明显的是,这是数据库的连接出了问题,因为这个错误是在sql执行时报的错。

  从tcp原理上分析,这个错误的原因是因为连接被无缘无故的关闭了,导致连接被重置。于是简单分析过后,怀疑是因为连接长时间没有使用已经失效,但是连接池依然把连接给了应用程序去使用,结果导致使用了已经失效的连接。

  于是LZ简单搜索了一下,发现很多人都说有一个属性可以控制在把连接交给数据源之前,先进行一下可用性的检测。于是LZ打开源码,看了一下这个属性的初始值,结果一看,发现是true。

1     /**
2      * The indication of whether objects will be validated before being
3      * borrowed from the pool.  If the object fails to validate, it will be
4      * dropped from the pool, and we will attempt to borrow another.
5      */
6     protected boolean testOnBorrow = true;

  这尼玛就奇怪了,已经是true了,那说明借用之前应该已经验证了,为毛还会出现上面的错误?

  LZ不服气,于是在本地启动了一下应用,跟踪了连接获取的过程,发现压根就没验证。于是LZ再次把整个数据源初始化的代码都看了一遍,才发现DBCP最坑爹的地方。看下面的代码。

1     // Can‘t test without a validationQuery
2         if (validationQuery == null) {
3             setTestOnBorrow(false);
4             setTestOnReturn(false);
5             setTestWhileIdle(false);
6         }    

  LZ看见的时候当时就TM想爆粗口了,这尼玛不是坑爹是什么?validationQuery默认就是空的,这不是相当于testOnBorrow默认是false吗,还在属性上写个true误导我等菜鸟程序猿。如果LZ不是闲着没事看了看初始化的源码,估计还在一直蛋疼这个问题,而且还要面临业务同事的鄙视。

  最终,设置了validationQuery属性以后,解决了这个小小的疑难杂症。各位猿友也要注意下,在使用DBCP时,最好设置一下validationQuery。

  

高端springmvc的滥用

  接下来的故事,是LZ滥用springmvc的故事,幸好LZ在上线之前就发现了这个问题,没有在合作伙伴面前丢人。

  随着公司的发展,需要与合作伙伴进行系统对接,于是LZ需要编写一个处于互联网上的服务端。上一篇博文里LZ简单介绍了加密的过程,本次则是后续LZ在做单元测试过程中发现的问题。

  由于服务端的调用会比较频繁,因此LZ在做单元测试的时候,专门写了并发访问的测试,期待能够简单的得到一个并发量的极限。结果却出乎意料,并发量极限没得到,却发现在跑的过程中,服务端爆了一些空指针错误。

  其实空指针错误也算是java当中最好解决的异常之一了,只要找到堆栈提示的位置,分析一下哪个表达式可能为空就基本上能解决问题。不过这次不同的是,LZ分析完以后,发现得到的结果是“不可能出现空指针异常”。

  怎么会不可能出现呢?看看下面这段简单的代码,这段代码不是真实的代码,但道理一样。

    user.setName("xiaolongzuo");
    //某一大堆与user无关的代码以后
    user.setSex("1");

  错误提示的是setSex那一行空指针,那么从代码上看,只有user为空时才会报空指针,但是假设user为空,那么在第一行就应该已经报了空指针错误,怎么可能到第三行才报出来呢?所以结论就是“不可能出现空指针异常”。

  后来LZ仔细分析之后才发现,LZ的结论是没错的,但那个结论的前提是程序按照代码编写的顺序执行。很明显,这是由于并发造成的,归根结底,是因为LZ以前从未用过springmvc,本次写服务端,由于希望发布restful风格的服务,因此选择了springmvc,抛弃了struts。

  springmvc的请求上下文是方法,struts的请求上下文是Action。LZ在代码当中错误的将请求作为了Action的属性出现,于是当并发访问时,请求中的参数就可能出现混乱,导致在第一行的时候user还不为空,到第三行的时候,由于请求被另外一个线程更新了,于是导致user为空,出现了奇葩的空指针,更加形象的代码如下。

    ((User)request.getAttribute("user")).setName("xiaolongzuo");
    //某一大堆与user无关的代码以后
    ((User)request.getAttribute("user")).setSex("1");

  解决办法有两种,第一种是去除Action中的request属性,保证线程安全,不过这样的话不少代码会出现编译错误。第二种是使用ThreadLocal,这种办法相对来说比较简单,而且不会出现编译错误,只需要简单的更改几行代码即可。

  最终,LZ采取了第二种办法,再次进行测试时,问题再也没有出现。为了保证错误真正解决了,LZ还特意加大了并发量,多测试了几次,依旧没有出现该问题。

小结

  本文没有高大上的技术,更多的算是LZ自己的一个问题记录,猿友们下次见!

时间: 2024-10-14 14:22:14

分享两个你可能不知道的Java小秘密的相关文章

【总结】你所不知道的Java序列化

我们都知道,Java序列化可以让我们记录下运行时的对象状态(对象实例域的值),也就是我们经常说的对象持久化 .这个过程其实是非常复杂的,这里我们就好好理解一下Java的对象序列化. 1. 首先我们要搞清楚,Java对象序列化是将 对象的实例域数据( 包括private私有域) 进行持久化存储.而并非是将整个对象所属的类信息进行存储. 其实了解JVM的话,我们就能明白这一点了.实际上堆中所存储的对象包含了实例域数据值以及指向类信息的地址,而对象所属的类信息却存放在方法区中.当我们要对持久层数据反序

你所不知道的Java序列化

我们都知道,Java序列化可以让我们记录下运行时的对象状态(对象实例域的值),也就是我们经常说的对象持久化 .这个过程其实是非常复杂的,这里我们就好好理解一下Java的对象序列化. 1. 首先我们要搞清楚,Java对象序列化是将 对象的实例域数据( 包括private私有域) 进行持久化存储.而并非是将整个对象所属的类信息进行存储. 其实了解JVM的话,我们就能明白这一点了.实际上堆中所存储的对象包含了实例域数据值以及指向类信息的地址,而对象所属的类信息却存放在方法区中.当我们要对持久层数据反序

深入洞见:你所不知道的Java 对象序列化的5件事儿

关于 Java 对象序列化的一些有用的小知识 不易理解,但对于解决 Java 编程挑战迟早有用. 将 Java 对象序列化 API它从一开始就存在于 JDK 1.1 中.本文介绍的关于序列化的 几件事情将说服您重新审视那些标准 Java API. Java 序列化简介 Java 对象序列化是 JDK 1.1 中引入的一组开创性特性之一,用于作为一种将 Java 对象的状态转换为字节数组,以便存储或传输的机制,以后,仍可以将字节数组转换回 Java 对象原有的状态. 到现在为止,还没有看到什么新鲜

讲一些你所不知道的Java动态代理

简介 Proxy 是设计模式中的一种.当需要在已存在的 class 上添加或修改功能时,可以通过创建 proxy object 来实现 通常 proxy object 和被代理对象拥有相同的方法,并且拥有被代理对象的引用,可以调用其方法 代理模式应用场景包括 在方法执行前后打印和记录日志 认证.参数检查 lazy instantiation (Hibernate, Mybatis) AOP (transaction) mocking - 代理有两种实现方式 静态代理:在编译时期,创建代理对象 动

你所不知道的java编程思想

读thinking in java这本书的时候,有这么一句话“在编译单元的内部,可以有一个公共(public)类,它必须拥有与文件相同的名字” 有以下疑问: 在一个类中说可以有一个public类,那是不是也就是说可以没有呢? 抱着这个问题进行了以下的实验:(文件名是Test1.java) class Test1 { public static void main(String[] args){ if(args != null && args.length > 0){ System.o

你所不知道的 Java 之 HashCode

之所以写HashCode,是因为平时我们总听到它.但你真的了解hashcode吗?它会在哪里使用?它应该怎样写? 相信阅读完本文,能让你看到不一样的hashcode. 使用hashcode的目的在于:使用一个对象查找另一个对象.对于使用散列的数据结构,如 HashSet.HashMap.LinkedHashSet.LinkedHashMap ,如果没有很好的覆写键的hashcode()和equals()方法,那么将无法正确的处理键. 请对以下代码中 Person 覆写hashcode()方法,看

(转)关于 Java 对象序列化您不知道的 5 件事

关于 Java 对象序列化您不知道的 5 件事 转自:http://developer.51cto.com/art/201506/479979.htm 数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数程序员对于 Java 平台都是浅尝则止,只学习了足以完成手头上任务的知识而已.在本 系列 中,Ted Neward 深入挖掘 Java 平台的核心功

你所不知道的五件事情--java.util.concurrent(第二部分)

这是Ted Neward在IBM developerWorks中5 things系列文章中的一篇,仍然讲述了关于Java并发集合API的一些应用窍门,值得大家学习.(2010.06.17最后更新) 摘要:除了便于编写并发应用的集合API外,java.util.concurrent还引入了其它的预置程序组件,这些组件能辅助你在多线程应用中控制和执行线程.Ted Neward再介绍了五个来自于java.util.concurrent的Java编程必备窍门. 通过提供线程安全,性能良好的数据结构,并发

关于Java你可能不知道的10件事

关于Java你可能不知道的10件事 分享到: 24 本文由 ImportNew - Jerry Lee 翻译自 Jooq.欢迎加入翻译小组.转载请参见文章末尾的要求. 呃,你是不是写Java已经有些年头了?还依稀记得这些吧: 那些年,它还叫做Oak:那些年,OO还是个热门话题:那些年,C++同学们觉得Java是没有出路的:那些年,Applet还风头正劲-- 但我打赌下面的这些事中至少有一半你还不知道.这周我们来聊聊这些会让你有些惊讶的Java内部的那些事儿吧. 1. 其实没有受检异常(check