CopyOnWriteArrayList和SynchronizedList的执行效率差别

CopyOnWriteArrayList和SynchronizedList这两者循环插入数据时,执行效率都有不利的地方,CopyOnWriteArrayList是每次都要弄个快照,添加完,把快照数据给主数据,再废掉快照,SynchronizedList则是到处加锁,加锁,就需要解锁。

今天闲来无事,自己写个demo来做测试,循环加一百万个数字。JDK版本1.8。
  1. Instant begin = Instant.now();
  2. long beginLong = begin.toEpochMilli();
  3. Collections.synchronizedList(new ArrayList<>(1048576));
  4. for(int i= 0; i<1_000_000; i++) {
  5. list.add(i);
  6. }
  7. Instant end = Instant.now();
  8. long endLong = end.toEpochMilli();
  9. System.out.println((endLong - beginLong));
使用SynchronizedList,执行时间:54ms
再用CopyOnWriteArrayList试试看
  1. Instant begin = Instant.now();
  2. long beginLong = begin.toEpochMilli();
  3. List<Integer> list = new CopyOnWriteArrayList<>(new ArrayList<>(1048576));
  4. for(int i= 0; i<1_000_000; i++) {
  5. list.add(i);
  6. }
  7. Instant end = Instant.now();
  8. long endLong = end.toEpochMilli();
  9. System.out.println((endLong - beginLong));
执行时间:598375ms,接近10分钟了。差距将近1000倍
再用CopyOnWriteArrayList的addAll试试看
  1. Instant begin = Instant.now();
  2. long beginLong = begin.toEpochMilli();
  3. List<Integer> list = Collections.synchronizedList(new ArrayList<>(1048576));
  4. for(int i= 0; i<1_000_000; i++) {
  5. list.add(i);
  6. }
  7. Instant end = Instant.now();
  8. long endLong = end.toEpochMilli();
  9. List<Integer> abcd = new CopyOnWriteArrayList<>();
  10. abcd.addAll(list);
  11. Instant end1 = Instant.now();
  12. long endLong1 = end1.toEpochMilli();
  13. System.out.println((endLong - beginLong));
  14. System.out.println((endLong1 - endLong));
执行结果:3ms。
  1. List<Integer> abcd = new CopyOnWriteArrayList<>(list);
直接将一个List作为初始化时的参数的执行时间也差不多是2ms-3ms。
所以,如果要循环插入大量信息,SynchronizedList的效率较高,CopyOnWriteArrayList基本可以放弃。但如果前面已经有获得的List数据,以addAll的方式或者使用构造方法一次性批量加入CopyOnWriteArrayList,这个效率还是很高的。

不常见的408错误

近日,用jenkins打包Java程序,并发布在容器中,打包发布并没有什么问题,当容器启动的时候,mybatis的配置文件发生了错误,是很不常见的408错误,报错:
Caused by: java.io.IOException: Server returned HTTP response code: 408 for URL: http://www.mybatis.org/dtd/mybatis-3-config.dtd 。但是重新发布就好了。

不过还是不要这么麻烦,害得容器都起不来,于是下载了dtd文件,放在src\main\resources目录下,并将配置文件相关部分修改为:
<!DOCTYPE configuration PUBLIC “-//mybatis.org//DTD Config 3.0//EN” “classpath:/mybatis-3-config.dtd”>

再次重新发布,OK。

升级Log4j到Log4j2的痛,找不到Zookeeper的类

日前项目将日志软件由Log4j升级到Log4j2,项目使用了dubbo做分布式服务,因此需要使用zkclient连接到Zookeeper集群。
使用Idea的依赖树图,将已有jar包中包含Log4j的,全部选择除去,然后引入Log4j2,slf4j。日志输出格式为Json,因此还需要引入jackson-core和jacksson-databind。修改完之后,将几个子项目一一发布。结果有三个服务报错,错误为
Caused by: java.lang.ClassNotFoundException: org.apache.zookeeper.Watcher$Event$KeeperState
顾名思义,这是表示org.apache.zookeeper.Watcher$Event$KeeperState这个类没找到。但仔细查证,发布的项目中是包含Zookeeper的jar包的。那为什么找不到呢?网上有篇文章说,是消费端启动的时候,服务端没处在服务状态,多次重连之后抛出异常,造成这个错误。这个观点结合我的情况来看,并不适用,报错的子项目所依赖的服务,都很稳定正常地在运行,不存在这种原因。

后来想起来,其他正常运行的子项目和这几个项目有什么区别呢?一比较Zookeeper和Zkclient的部分,果然,exclusions部分不一样,不正常的子项目的zkClient没有这一部分,正常的包含这一部分。于是按照正常的子项目的pom.xml文件进行修改,去除了有关Log4j的依赖,再次发布,项目正常启动了。

综合分析,是因为依赖关系出错,造成zkClient不能正常初始化,因此在调用远程的dubbo服务时,就不能正常连到Zookeeper集群,在程序看来,就类似于消费端启动的时候,服务端没处在服务状态,多次重连之后抛出异常的情况。因此会有这个错误。

Spring读取配置文件的中文值乱码问题的解决

日前做项目,发现以@Value注解读取properties文件中的中文值出现乱码,造成bug,上网查找,发现有N种方案,神马在idea、Eclipse中显示正常的统统不看,我是要保证程序执行的时候正确,IDE编辑的时候查看是不是正确并不重要。

方案1,将中文字转成unicode字符,存在properties文件中,这是最简单的托底方案,可以用来救急。但这样的properties文件可读性实在太差。

方案2,使用yaml格式或者xml格式。改用了yaml格式,咦,还是乱码,放弃。

方案3,使用@PropertySource注解来代替XML里的配置。咦,还是乱码,放弃。

@PropertySource(value = "classpath:xxx.properties",encoding = "UTF-8")

方案4,在context:property-placeholder标签中加入file-encoding属性。咦,还是乱码,放弃。

<context:property-placeholder location="classpath:xxx.properties" file-encoding="UTF-8"/>

嗯?这些方案都乱码,难道非得用方案1吗?等下,一定有哪里忽略了。

再仔细看了下,在context:property-placeholder标签中使用前,必须在PropertyPlaceholderConfigurer的bean中登记,这个地方有没有编码配置呢?加上编码配置,果然OK。

<property name="fileEncoding" value="UTF-8"></property>

现在回想,方案2、方案3,是不是都是因为缺少这一行代码造成还是乱码的呢?目前还没验证。

另类原因导致的Too many open files错误

最近做开发,本地写好的程序没有问题,提交到局域网内的测试平台上,就经常报Too many open files错误,然后也经常报No reachable node in cluster错误(连接不到redisCluser)。

上网去搜,发现无一例外地,都说Too many open files错误是由于linux对文件句柄的限制数过小造成的,我们的环境也的确这个值较小,是默认的1024,让运维同学提高了还是一样。

No reachable node in cluster错误也是一样,redis的端口号能够telnet通,使用命令行直接在redis集群的服务器上操作也很正常,就是项目访问时,经常出现这个错误。

烦躁,在网上不停地换各种关键字搜索,最后找到了这篇文件《Java IO Stream句柄泄露分析》,里面提到:

当文件流未被显式关闭时,会产生怎样的后果?结果就是会引起文件描述符(fd)耗尽。以下代码会打开一个文件10次,向系统申请了10个fd。
……
但是即便如此,依然存在资源泄漏导致程序无法正常工作的情况,因为JVM规范并未对GC何时被唤起作要求,而对象的finalize()只有在其被回收时才触发一次,因此完全存在以下情况:在两次GC周期之间,文件描述符被耗尽!这个问题曾经在生产环境中出现过的,起因是某同事在原本只需加载一次的文件读取操作写成了每次用户请求加载一次,在一定的并发数下就导致too many open file的异常:

立刻以关键字input、output遍历整个项目,果然发现很多没有关闭流的文件,尤其是读取配置文件的那一个,也没有关闭流。这样原因就很明显了,因为没有关闭读取文件的流,加之文件句柄数较小,文件描述符很快用尽,再次访问时如果需要读取配置文件,就因为没有文件描述符,无法读取。配置都无法读取到,当然不可能建立对redis的连接。遇上个别添加了关闭流语句的程序,流被关闭,文件描述符被释放出来一个两个,于是在这之后的访问就可以正确读取到配置文件,连接到redis,从而程序可以正确访问。但是因为个数有限,文件描述符又很快被用尽,于是后面的人又无法正确访问了。

本地测试因为几乎没有并发,文件描述符被消耗的速度很慢,因此没有出现这个问题。

横展开修复了所有未关闭流的文件,果然,Too many open files错误再没有出现过

java删除cookie的注意事项

最近做项目,java删除cookie没删除干净,造成退出后还有cookie,可以直接返回有权限的页面。
看到http://www.itnose.net/detail/6217972.html有这样一句话:“需要注意的是,cookie的path一定要和你想要删除的cookie的path匹配,不然只会把sessionId的值设置为空,而不会删除该cookie。”,经过仔细比对发现,我在退出的时候设置cookie,设置了domain和path,但是在登录的时候,没有设置这两个值,之前因为在本地开发,域名是localhost,可能是cookie的默认值,所以删掉了。到测试环境的时候,因为设置了正式的域名,两边对应不上,所以删除不掉。
知道了原因就很容易修正了。在登录的时候,也给cookie加上domain和path的设置就OK了。果然顺利删除,不能再直接返回有权限的页面了。

Dubbo cvc-complex-type.2.4.c 错误,dubbo.xsd

来自:alexgaoyh的个人空间 – 开源中国社区

项目引入Dubbo之后,XML文件报错: 

  1. Multiple annotations found at this line: - cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'dubbo:service'. - schema_reference.4: Failed to read schema document 'http://code.alibabatech.com/schema/dubbo/dubbo.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
如下图所示:

 

其实在项目中也不影响使用,只是强迫症的话,看着感觉是项目报错,就想着修改一下这里的配置:

1: 解压缩 Dubbo-*.jar 文件,在文件中找到 dubbo.xsd 文件;

2:windows->preferrence->xml->xmlcatalog
add->catalog entry  ->file system 选择刚刚下载的文件路径
修改key值和配置文件的http://code.alibabatech.com/schema/dubbo/dubbo.xsd 相同
保存。。在xml文件右键validate  ok解决了。

【原创】MySQL Workbench新建JSON字段

在MySQL  WorkBench中做ER图设计,并要将其转换为数据库,数据库的版本为MySQL 5.7.18。
按照需求设置了一个字段为5.7.8之后才有的新的JSON类型,按照习惯,为这个字段设置了字符集为UTF8,设置规则为UTF8_GENERAL_CI,结果在Forward Database的时候,SQL语句报错。
  1. Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NULL
百思不得其解,最后把字符集设置去掉,就顺利通过,建表成功。特地做个记录。

 

Java 1.8新建Maven SSM项目笔记(九)

数据库字段与lombok

Entity类通常都要实现该类所有属性的getter/setter方法,如果使用lombok,可以完全省略该步,只书写属性,并在Entity类的开头加上@Data注解,并导入对应jar包,就可以由lombok在编译时自动生成toString方法、 hashcode方法、equals方法、所有属性的普通getter/setter方法。

当然,如果你需要自定义某个属性的getter/setter方法,可以自己书写具体的方法,该方法在编辑器上会出现override的三角形标记。即覆盖了lombok的对应方法。

在eclipse中使用lombok时,需要做以下设置,否则eclipse会报所有属性的普通getter/setter方法未定义。

执行lombok.jar

或者在eclipe.ini中末尾添加以下内容:

-Xbootclasspath/a:lombok.jar
-javaagent:lombok.jar