Java开发最佳实践(一) ——《Java开发手册》之"编程规约" (7)

并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据【说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。】

多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,如果在处理定时任务时使用ScheduledExecutorService则没有这个问题

资金相关的金融敏感信息,使用悲观锁策略。【说明:乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞,另外,乐观锁对冲突的解决策略有较复杂的要求,处理不当容易造成系统压力或数据异常,所以资金相关的金融敏感信息不建议使用乐观锁更新。】

使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果【说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。】

避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致的性能下降【说明:Random实例包括java.util.Random的实例或者Math.random()的方式。正例:在JDK7之后,可以直接使用APIThreadLocalRandom,而在JDK7之前,需要编码保证每个线程持有一个实例】

在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解决方案中较为简单一种(适用于JDK5及以上版本),将目标属性声明为volatile型。

// 注意 这里的代码并非出自官方的《java开发手册》 // 参考 https://blog.csdn.net/lovelion/article/details/7420886 public class LazySingleton { // volatile除了保证内容可见性还有防止指令重排序 // 对象的创建实际上是三条指令: // 1、分配内存地址 2、内存地址初始化 3、返回内存地址句柄 // 其中2、3之间可能发生指令重排序 // 重排序可能导致线程A创建对象先执行1、3两步, // 结果线程B进来判断句柄已经不为空,直接返回给上层方法 // 此时对象还没有正确初始化内存,导致上层方法发生严重错误 private volatile static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { // 第一重判断 if (instance == null) { synchronized (LazySingleton.class) { // 第二重判断 if (instance == null) { // 创建单例实例 instance = new LazySingleton(); } } } return instance; } } // 既然这里提到 单例懒加载,还有这样写的 // 参考 https://blog.csdn.net/lovelion/article/details/7420888 class Singleton { private Singleton() { } private static class HolderClass { // 由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次 final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return HolderClass.instance; } }

volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。【说明:如果是count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。】

HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险

ThreadLocal对象使用static修饰,ThreadLocal无法解决共享对象的更新问题【说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量】

(七) 控制语句

当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。

public class SwitchString { public static void main(String[] args) { // 这里会抛异常 java.lang.NullPointerException method(null); } public static void method(String param) { switch (param) { // 肯定不是进入这里 case "sth": System.out.println("it's sth"); break; // 也不是进入这里 case "null": System.out.println("it's null"); break; // 也不是进入这里 default: System.out.println("default"); } } }

在if/else/for/while/do语句中必须使用大括号。【说明:即使只有一行代码,避免采用单行的编码方式:if (condition) statements;】

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdgff.html