java中但凡涉及对象赋值的地方基本都是拷贝的引用,包括等号赋值、集合框架中的添加对象等。在需要用到clone时先考虑清楚是否确实需要对象的内存副本,再结合需要的拷贝深度去实现对象内部包含对象的clone方法,有时可能还要进一步实现对象的对象的对象中的clone方法
9.Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常
(1) x,y的比较结果和y,x的比较结果相反。
(2) x>y,y>z,则x>z。
(3) x=y,则x,z比较结果和y,z比较结果相同。
这里要注意的是在实现compare的方法时,要对两个对象的‘>‘,‘<‘,‘=‘三种情况都要考虑到。‘=‘的情况往往是容易被忽略的。
10.集合初始化时,指定集合初始值大小。
这一点在项目代码中也是经常被忽略的一点,未指定初始大小的集合,会有一个默认值。在容量元素不断增加,超过限度时,集合会进行resize的操作,这里涉及到对象的引用复制。在强调性能的应用中,需要考虑到这一点。
11.使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。
这里也是基于性能考虑。在遍历Map时我们通常会用到键和值,使用entrySet遍历的话再加上map.get("key"),会多一次遍历的过程。
12.线程池不要使用Executors去创建,而是通过ThreadPoolExecutor的方式
通过Executors创建的4个常见的线程池存在如下弊端:
FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
在多线程场景下尽量使用ThreadPoolExecutor创建线程池,根据实际业务场景和并发情况合理设置核心线程数、最大线程数、任务队列类型(阻塞或非阻塞)、队列长度等参数。可以边设置边调试,找到最适合业务运行的参数配置。
13.并发场景下使用锁时的考虑
锁是在多线程编程场景下基于线程安全对线程公共资源的访问控制手段,其功能实现是以性能开销为代价的。使用时遵循以下原则:
能用无锁方式实现,尽量用无锁的方式
锁的粒度尽量小(能锁区块,就不要锁方法;能锁对象,就不要锁类)
在写少读多的场景,推荐用乐观锁的方式(锁失败重试的方式,而非阻塞式的锁)。java的concurrent包中的锁实现就是基于CAS的乐观锁
14.volatile使用注意事项
volatile解决的是java多线程下的内存可见性问题,保证了变量的写操作对读操作的可见性(底层实现方式为编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序)。当操作是非原子操作时,单纯依靠对变量使用volatile就会有问题。
例如,在执行简单的count++的操作时,该操作是由变量加1和变量赋值两个步骤构成,只对变量volatile在多线程统计场景中会出现统计少于实际值的情况。这时应该尝试使用concurrent包中的AtomicInteger(基于CAS的乐观锁实现,该类存在的ABA问题结合实际使用场景决定是否需要考虑)
15.在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。