Effective Java读书笔记

1.活性失败
    活性失败类似于可见性失败,由于数据没有同步导致共享数据没有及时可见,导致依赖该数据的值的线程出现异常。过度同步的意思是在同步块调用外来方法导致同步方法覆盖面大,即为了代码编写方便而把同步块设置得过大,可能不是数量太多的同步块。

2.builder构建者模式
    builder模式实际上是针对使用工具的人,例如我开发了这个工具,因为我自己清楚代码的逻辑,所以我基本上能保证对象的一致性和完整性,但是当别人使用的时候,或许会漏掉某些参数,所以需要使用builder来保持对象的一致性和完整性

3.单例模式的并发问题
new对象的3步操作:
1)java堆内存分配(此时已经获取到该对象的入口地址和内存大小)
2)对象初始化(往内存中写数据)
3)变量赋值(把入口地址赋值给变量)
    jdk1.5前,volatile的语义不够明确,创建对象的代码前后指令的确没有重排序,但是创建对象的这3个步指令却存在重排序,对象没有完成初始化,就已经把入口地址赋值给变量导致其他线程看到变量不为null而使用一个未完全初始化的对象。
    在使用单例模式时,依然需要加volatile关键字,否则创建对象的3步操作指令依然存在重排序的可能,在这里volatile关键字的作用就不是让各线程立刻可见了,因为同步块本身就有这个功能。


优化前的双重检查模式
1 private volatile FieldType field;

2 private FieldType getField(){
3   if(field== null){
4     synchronized(this){
5       if(field == null){
6         field = computeValue();
7       }
8     }
9   }
10   return field;
11 }

优化后的双重检查模式
1 private volatile FieldType field;

2 private FieldType getField(){
3   FieldType result = field;//使用局部变量获取volatile变量值
4   if(result == null){
5     synchronized(this){
6       if(result == null){
7         field = result = computeValue();
8       }
9     }
10   }
11   return result;
12 }

    return 也会再次读取变量的值,如果是valotile变量,则会再次读取主内存的值
    return会读取主内存的原因是:万一线程1判断volatile变量为null且在return之前,volatile变量被线程2初始化之后并退出同步块,volatile变量已经赋值了,并立刻写入主内存,假设线程1的return不去重新读取主内存而使用工作内存的值(即为未初始化的null),而线程2就获取到工作内存中已经初始化的值,这样线程1和2就获取到不一致的对象了。

    在上面优化前的单例模式的代码中,因为在第3行第一次判断是否为null时读取一次,第5行第二次判断时又读取了一次,第10行return时又读取了一次,就是读取了3次
    优化后的代码中,第3行使用局部变量result先获取volatile变量的值再操作,确保只在已经被初始化的情况下读取一次。这里说的已经被初始化有2种情况:
第一种是第一层判断已经被初始化了,1个if判断,volatile变量都只读取一次。
第二种是第二层判断才被初始化,2个if判断,volatile变量都只读取一次。

4.泛型的继承关系
    当参数化类型T在类级别上时,T为客户端在使用类时定义的类型,通常在创建对象后,该类型就会随着该对象的生命周期而固定。对于某个方法的参数,入参的参数化类型为S(S是客户端具体调用时传递的,具有可变性)
    假如方法形参是类型S的生产者(把该入参赋值给以T定义的变量上),则类型T是生产者类型S的父类或本身,可以用? extends T来定义方法参数的泛型,?代表类型S
    假如方法形参是类型S的消费者(把以T定义的变量赋值给该入参),则消费者类型S是类型T的父类或本身,可以用? super T来定义方法参数的泛型
    在方法中,假如入参和出参是同一个的参数化类型变量,当没有使用有限制通配符时,入参和出参是同一个类型(用户指定的类型),假如在入参或出参中使用了有限制的通配符,则?作为用户具体指定的类型,参数化类型变量作为类型限制。例如List<? extends T> f(Class)、List f(Class<? extends T>)、List<? super T> f(Class)和List f(Class<? super T>)
    在定义泛型参数的具体类型时,是给T,S等这些参数设值,因为泛型参数没有继承关系,如果没有使用extends或super,在编译时虽然是子类可以赋值给定义类型为父类的变量,但是实际运行时会报转换异常,因为泛型在编译后其实就是加上强制转换类型的代码

5.extends的含义
    extends被翻译为继承或扩展。继承的含义是继承父类的所有属性和方法,使子类能够复用父类的属性和代码而不需要重复编写相同的代码。扩展的含义是子类扩展父类的功能,使父类在不需要更改代码的情况下进行增强。在使用extends时,应该回归他的本质,子类是否是父类的子类型,如果仅仅只是为了复用代码,则应该使用复合而不是继承。

posted @   分享读书笔记的程序员  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示