3.【Java开发手册】| 编程规约(二)

这里我就列出一些我认为应当遵守的,并且添加一些我自己在工作中的一些感受,如果照着文档抄,那完全变成练习打字了,浪费读者时间,如果你也认同我的看法,或者和我有类似感受,可以点个关注,共同进步,如果有不同意见,欢迎指出。

四、OOP 规约

  1. 【强制】静态变量和静态方法直接用类名来访问
  2. 【强制】所有重写的方法,必须加 @Override 注解
  3. 【强制】相同的参数类型,相同业务含义,才可以使用可变参数,参数类型避免定义为Object

可变参数放置在参数列表最后。建议不用可变参数编程

  1. 【强制】禁止使用可变参数

可变参数,不易于理解,并且可变参数带来的收益相比对项目带来的负面影响并不值得推广。

  1. 【强制】外部正在投入使用中的方法或者接口,不允许修改方法签名,避免发生异常影响。过时的接口要加@Deprecated注解,并且清晰的说明采用的新接口或者新的服务是什么
  2. 【强制】不能使用过时的方法

这个没什么好说的,方法过时了,新的方法要么性能更好,要么就是更加安全,所以既然过时了就不要再用了。如果不加这条约束,就会导致一些CV大师,到处 CV,抱着反正能用就行的态度,到处贴狗皮膏药,等到版本升级的时候就知道痛苦了。

  1. 【强制】equals 比较的时候,将有明确的值对象放在前面,可以避免 NPE

这是个小细节,注意下,推荐使用jdk自带工具类,Object.equals(Object A , Object B)

  1. 【强制】所有整形包装类对象之间值的比较全部使用 equals

这里就涉及到一个常见面试题了,Integer a = 128, integer b = 128, sout(a==b),问:输出结果是什么?相信get 到点的都知道是 false,这主要就是由于整形的包装类型,在 -128 - 127 之间会复用对象,这个时候使用 == 比较是没有问题的,但是当超过这个范围后,相当于不同的对象,使用 ==去比较两个地址值,肯定是 false 啦!不过这个错误倒不是很常见了,因为现在的 IDEA 基本都会给出提示,如果你使用 ==比较时会有警告。

  1. 【强制】任何货币金额,均以最小货币单位且为整型类型进行存储

整型的计算效率要高于decimal,但是具体快多少我没有具体测试过,也没有看过相关数据,不是特别清楚,但是我们一般直接使用 decimal 类型,并不做强制要求。可能阿里有自己的考量。

  1. 【强制】浮点数之间等值判断,基本类型不能用 == 进行比较,包装类型不能用 equals 进行判断。

我们禁止使用浮点数类型,所以涉及到小数计算的一律用 BigDecimal

如果接手了老项目,要知道,如果是浮点型比较,指定一个误差范围,两个浮点的差值在范围内,则认为相等。

float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
float diff = 1e-6F;
if (Math.abs(a - b) < diff) {
    System.out.println("true");
}
  1. 【强制】BigDecimal 的等值比较使用 compareTo() 方法,而不是使用 equals()

equals 比较值会比较精度,比如 1.0 与 1.00 比较会是 false

  1. 【强制】定义数据对象 DO 类时,属性类型要与数据库类型字段匹配

这个基本上现在框架都帮我们做好了,bigint 对应的 Long,不刻意去作死乱改一般没什么问题。

  1. 【强制】禁止使用构造方法BigDecimal(double) 的方式把 double 转成 BigDecimal

这个确是是个比较常见的问题,在过去CodeReview的时候经常会看到,因为大家都知道 double 直接计算会有精度问题,所以转成了 BigDecimal,但是如果直接通过构造器转换浮点值,还是会出现精度问题,要求在转换的时候一律只允许使用 String 类型的构造器,或者使用 BigDecimal 的 valueOf() 方法,这个方法会执行Double的toString()方法,会对实际表达的精度进行截断。

  1. 关于基础类型与包装类型使用标准:
    1. 【强制】所有POJO都必须使用包装类型
    2. 【强制】RPC 方法的返回值和参数必须使用包装类型
    3. 【推荐】所有局部变量使用基本类型

这个主要是由于数据库查询结果可能为 null , 如果 POJO 类使用基础类型就有可能出现 NPE

  1. 【强制】定义 DO/PO/DTO/VO 等 POJO 的时候不要设定任何默认值

这个阿里原文给出的反例是,有人给创建时间设了默认值,new Date() 导致更新的时候出现覆盖的问题,这个一般人不会这么干,我举一个真实碰到的问题,给一些状态字段默认值。前端明明没有传值,但是状态确不是预期的状态了,最后排查下来是因为有默认值。 从另一个角度来说,所有的字段接收的值应当由调用者来控制负责,我还能赞同一点。

  1. 【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列化失败;如果完全不兼容升级,避免反序列化混乱,请修改 serialVersionUID 字段

遵守就行了,默认规则

  1. 【强制】构造方法里禁止加入任何业务逻辑,如果有初始化逻辑,放在 init 方法中。

构造方法唯一作用就是构造对象,如果加逻辑就会导致一些意外情况发生。

  1. 【强制】POJO 类必须写 toString 方法

这个主要是便于排查问题,打印属性值。然后还顺便提了一下如果使用到了继承,子类手写或者自动生成toString的时候加一下super.toString()方便打印父类属性值,但是我们一般会用lombok 注解,下面写法等于加上@ToString(callSuper = true)

  1. 【强制】禁止在POJO类中,同时存在对应属性 xxx 的 isXxx() 和 getXxx() 方法

有些框架无法确定通过isXxx 还是 getXxx 获取属性,神坑之一。不过我倒是没有遇到过。

  1. 【推荐】使用索引访问用 String 的 split 方法的到的数组时,需做最后一个分隔符后有无内容的检查,否则会抛出异常。

这个主要是他担心会出现 “a,b,c,” 这种情况

  1. 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读,此条规则优先于下一条。
  2. 【推荐】类内方法定义的顺序依次是:公有方法或保护方法>私有方法>getter/setter方法。

这个没什么特殊要求,可以参考,其实主要目的就是将使用时关心的核心方法放在首屏展示,但是一般都是鼠标点击跳转,所以,问题不大。

  1. 【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名=参数名。在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。

这个一般情况下都是直接生成的,正常开发中也没人会去改。

  1. 【推荐】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。

这个 IDEA 会提示你的,所以没什么好说的

  1. 【推荐】final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
    1. 不允许被继承的类,如:String 类。水平有限,我是从来没用过
    2. 不允许修改引用的域对象,如 POJO 类的域变量
    3. 不允许被覆写的方法,如:POJO 类的 setter 方法
    4. 不允许运行过程中重新赋值的局部变量
    5. 避免上下文重复使用一个变量,使用 final 关键字可以强制重新定义一个变量,方便更好的进行重构
  2. 【推荐】慎用Object的clone方法来拷贝对象

对象的clone方法默认是浅拷贝,如果想实现深拷贝需要重写 clone 方法,实现域对象的深度遍历式拷贝

  1. 【推荐】类成员与方法访问控制从严
    1. 如果不允许外部通过 new 来创建对象,那么构造方法必须是 private
    2. 工具类不允许有 public 或 default 构造方法,工具类一般提供的都是静态方法,不应该允许被实例化
    3. 类非 static 成员变量并且与子类共享,必须是 protected,
    4. 类非 static 成员变量并且只在本类中使用,必须是 private
    5. 类 static 成员变量只在本类中使用,必须是 private
    6. 若是 static 成员变量,考虑是否为 final
    7. 类成员方法只供类内部调用,必须是 private
    8. 类成员方法只对继承类公开,那么限制为 protected

五、日期时间

  1. 【强制】日期格式化时,传入pattern中表示年份统一使用小写的y。

从 JDK17 开始,YYYY 表示本周所属的年份,如果存在跨年情况,就会不符合预期了

  1. 【强制】在日期格式中分清楚大写的M和小写的m,大写的H和小写的h分别指代的意义。

这个我感觉就是阿里凑规约条数,这个程序员还能不清楚这个吗?那不是写代码都靠蒙?

  1. 【强制】获取当前毫秒数:System.currentTimeMillis();而不是newDate().getTime()。

获取纳秒级时间,则使用System.nanoTime的方式。在JDK8中,针对统计时间等场景,推荐使用Instant类。

  1. 【强制】不允许在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。

这个主要还是使用习惯的问题,而且网上百度的代码大多数也是基于这些个类的。JDK8 后统一规定时间使用LocalDate 和 LocalTime,LocalDateTime 以及 Instant 对象。

// 获取当前日期
LocalDate currentDate = LocalDate.now();
System.out.println("Current date: " + currentDate);

// 获取当前时间
LocalTime currentTime = LocalTime.now();
System.out.println("Current time: " + currentTime);

// 获取当前日期和时间
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println("Current date and time: " + currentDateTime);

// 获取当前日期和时间,并指定时区
ZonedDateTime currentZonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("Current date and time with timezone: " + currentZonedDateTime);

// 获取当前日期和时间,并加上偏移量
OffsetDateTime currentOffsetDateTime = OffsetDateTime.now();
System.out.println("Current date and time with offset: " + currentOffsetDateTime);
  1. 【强制】禁止在程序中写死一年为365天,避免在公历闰年时出现日期转换错误或程序逻辑错误。

这个也是凑条数的,有常识的都知道有闰年的存在

  1. 【推荐】避免公历闰年2月问题。闰年的2月份有29天,一年后的那一天不可能是2月29日。

这个也是一样,凑条数

  1. 【推荐】使用枚举值来指代月份。如果使用数字,注意Date,Calendar等日期相关类的月份month取值范围从0到11之间。

六、集合处理

  1. 【强制】关于hashCode和equals的处理,遵循如下规则:
    1. 只要覆写 equals,就必须覆写 hashCode
    2. 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须覆写这两种方法。
    3. 如果自定义对象作为Map的键,那么必须覆写hashCode和equals。

其实本质目的是进行对象比较的时候,使用 equals 判断,最终需要使用到 hashCode 方法生成的 hash 值,特别是在一下集合类中。

  1. 【强制】断所有集合内部的元素是否为空,使用isEmpty()方法,而不是 size()==0 的方式。

IDEA 会提示, 在某些集合中,前者的时间复杂度为O(1),而且可读性更好。

  1. 【强制】在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要使用参数类型为BinaryOperator,参数名为mergeFunction的方法,否则当出现相同key时会抛出IllegalStateException异常。

这个其实遵守Api规范就行

// 反例:不带冲突解决策略
Map<K, V> map = stream.collect(Collectors.toMap(keyMapper, valueMapper));  
// 正例:带冲突结局策略
Map<K, V> map = stream.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction));
  1. 【强制】在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当value为null时会抛NPE异常。

这里可以看出,Stream 流操作确实带来了便利,但是使用不好也是一把双刃剑,注意一下就好。

  1. 【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList

subList()返回的是ArrayList的内部类SubList,并不是ArrayList本身,而是ArrayList的一个视图,对于SubList的所有操作最终会反映到原列表上。 这个我没怎么用过,不过既然都会报异常了,一般也不会有什么问题。

  1. 【强制】使用Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常。

凑条数的,除非不测试,不然不可能这么写的。

  1. 【强制】Collections类返回的对象,如:emptyList()/singletonList()等都是immutable list,不可对其进行添加或者删除元素的操作。

凑条数,你这么干会报异常的,除非你不测试。

  1. 【强制】在subList场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生ConcurrentModificationException异常。

抽查表明,90%的程序员对此知识点都有错误的认知。确实在看到这点的时候我也没有注意过,不过你真这么干的时候会报错的

  1. 【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一致、长度为0的空数组。

使用toArray带参方法,数组空间大小的length:

1)等于0,动态创建与size相同的数组,性能最好。

2)大于0但小于size,重新创建大小等于size的数组,增加GC负担。

3)等于size,在高并发情况下,数组创建完成之后,size正在变大的情况下,负面影响与2相同。

4)大于size,空间浪费,且在size处插入null值,存在NPE隐患。

  1. 【强制】使用Collection接口任何实现类的addAll()方法时,要对输入的集合参数进行NPE判断。

这个要稍微注意一下,确实可能存在这个问题。

  1. 【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。

asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

String[]str=newString[]{“yang”,“guan”,“bao”};

Listlist=Arrays.asList(str);

第一种情况:list.add(“yangguanbao”);运行时异常。

第二种情况:str[0]=“change”; list中的元素也会随之修改,反之亦然。

  1. 【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,两者在接口调用赋值的场景中容易出错。

扩展说一下PECS(ProducerExtendsConsumerSuper)原则,即频繁往外读取内容的,适合用<? extends T>,经常往里插入的,适合用<? super T>

  1. 【强制】在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof判断,避免抛出ClassCastException异常。

目前环境下基本都是基于JDK1.8+的,所以不用考虑兼容老版本问题,要求必须指定泛型。

  1. 【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用iterator方式,如果并发操作,需要对iterator对象加锁。
// 正例
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
   String item = iterator.next();
   if ("1".equals(item)) {
       iterator.remove();
   }
}

log.info("list:{}", list);
// 反例

for (String item: list) {
    if ("1".equals(item)) {
        list.remove(item);
    }
}
// 如果把这里的 “1” 换成 “2” 会报异常,因为不允许在迭代一个集合的同时修改该集合,必须通过迭代器来实现。
  1. 【强制】在JDK7版本及以上,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比 较结果相同。

// 正例
import java.util.Comparator;

public class StringLengthComparator implements Comparator<String> {

    @Override
    public int compare(String s1, String s2) {
        if (s1 == null || s2 == null) {
            throw new NullPointerException("Strings cannot be null");
        }
        
        int lengthDifference = s1.length() - s2.length();
        if (lengthDifference == 0) {
            return s1.compareTo(s2);  // 如果长度相同,则按字典序比较
        } else {
            return lengthDifference;  // 否则按长度比较
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof StringLengthComparator) {
            return true;  // 假设所有的实例都等价
        }
        return false;
    }
}
  1. 【推荐】泛型集合使用时,在JDK7及以上,使用diamond语法或全省略。
// diamond方式,即<>
HashMap<String,String> userCache=new HashMap<>(16);
// 全省略方式
ArrayList<String> users =new ArrayList(10);
  1. 【强制】必须使用 diamod 语法

统一代码风格,没有为什么

  1. 【推荐】集合初始化时,指定集合初始值大小。

说明:HashMap使用构造方法HashMap(intinitialCapacity)进行初始化时,如果暂时无法确定集合大小,那么指定默认值(16)即可。

正例:initialCapacity=(需要存储的元素个数/负载因子)+1。注意负载因子(即loaderfactor)默认为0.75,如果暂时无法确定初始值大小,请设置为16(即默认值)。

反例:HashMap需要放置1024个元素,由于没有设置容量初始大小,随着元素增加而被迫不断扩容,resize()方法总共会调用8次,反复重建哈希表和数据迁移。当放置的集合元素个数达千万级时会影响程序性能。

  1. 【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。

说明:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.forEach方法。正例:values()返回的是V值集合,是一个list集合对象;keySet()返回的是K值集合,是一个Set集合对象;entrySet()返回的是K-V值组合的Set集合。

19.【推荐】高度注意Map类集合K/V能不能存储null值的情况,如下表格:

集合类keyvalue说明
Hashtablenot nullnot null线程安全
TreeMapnot nullnull线程不安全
ConcurrentHashMapnot nullnot null线程安全 (锁分段技术,JDK8:CAS)
HashMapnullnull线程不安全
  1. 【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。

说明:有序性是指遍历的结果是按某种比较规则依次排列的,稳定性指集合每次遍历的元素次序是一定的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。

  1. 【参考】利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains()进行遍历去重或者判断包含操作。

这也是以前的一个常见面试题,怎样快速去重

七、并发处理

  1. 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。

说明:资源驱动类、工具类、单例工厂类都需要注意。

  1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
  2. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
  3. 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

1)FixedThreadPoolSingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

3)ScheduledThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

  1. 【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。

这个我不是特别理解,先遵守,后续补充一下线程安全方面的知识。

// 正例:注意线程安全,使用DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> dateStyle = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};

说明:如果是JDK8的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutablethread-safe。

  1. 【强制】必须回收自定义的 ThreadLocal 变量记录的当前线程的值,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代码中使用try-finally块进行回收。
objectThreadLocal.set(userInfo);
try {
    // ...
} finally {
    objectThreadLocal.remove();
}
  1. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。 这个主要是RPC方法的不确定性,会导致锁的释放出现问题,可能出现死锁的问题

  1. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。

  1. 【强制】在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。

说明一:在lock方法与try代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。

说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。

说明三:在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。

// 正例
Lock lock = new XxxLock();
// ...
lock.lock();
// 这里不要有任何可能抛出异常的方法,如果这里抛出异常,会导致锁无法释放
try {
    doSomething();
    doOthers();
} finally {
    lock.unlock();
}

// 反例
Lock lock = new XxxLock();
// ...
try {
    doSomething();  // 如果这个方法抛出异常,后面不执行了,会导致,没有加锁的资源去释放锁,会出现异常
    lock.lock();
    doOthers();   
} finally {
    lock.unlock();
}
  1. 【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。

如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。

  1. 【强制】多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
  2. 【推荐】资金相关的金融敏感信息,使用悲观锁策略

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

正例:悲观锁遵循一锁二判三更新四释放的原则。

  1. 【推荐】使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果。

说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。

  1. 【推荐】避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致的性能下降。

说明:Random实例包括java.util.Random的实例或者Math.random()的方式。

正例:在JDK7之后,可以直接使用APIThreadLocalRandom,而在JDK7之前,需要编码保证每个线程持有一个单独的Random实例。

  1. 【推荐】通过双重检查锁(double-checked locking),实现延迟初始化需要将目标属性声明为volatile型,(比如修改helper的属性声明为private volatile Helper helper=null;)。
// 正例:
public class LazyInitDemo {
    private volatile Helper helper = null;

    public Helper getHelper() {
        if (helper == null) {
            synchronized (this) {
                if (helper == null) {
                    helper = newHelper();
                }
            }
        }
        return helper;
    }
    // other methods and fields...
}

暂时理解不了为什么,后续补充线程安全方面知识

  1. 【参考】volatile解决多线程内存不可见问题对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

说明:如果是 count++ 操作,使用如下类实现:

Atomic Integer count=new AtomicInteger();

count.addAndGet(1);

如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。

  1. 【参考】HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中注意规避此风险。
  2. 【参考】ThreadLocal对象使用static修饰,ThreadLocal无法解决共享对象的更新问题。

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

发现对于并发处理以及线程安全方面,自己理解起来还是比较吃力,这方面还有待提高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曹申阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值