Effective java 系列之避免过度同步和不要使用原生态类型,优先考虑泛型

时间:2022-08-06 08:40:28

避免过度同步(67):在一个被同步的方法或代码块中,不要调用哪些被设计成被覆盖的方法或者是由客户端以函数对象的形式提供的方法(21)。

有点拗口,书上提供的创建者与观察者模式,add方法太多,看得眼花缭乱,重新写了一个例子:

package com.example.demo.effective;

public interface Believer {
void confide();
}
package com.example.demo.effective;

import com.google.common.collect.Lists;

import java.util.List;

/**
* 上帝
*/
public class God {
/**
* 天国
*/
private List<Believer> believers = Lists.newArrayList(); public void addBeliever(Believer be){
synchronized (believers){
believers.add(be);
}
} public void removeBeliever(Believer be){
synchronized (believers){
believers.remove(be);
}
} public void getBeliever(Believer be){
synchronized (believers){
//选一个大主教
System.out.println(believers.get(0));
}
} /**
* 倾听每位信徒倾述
*/
public void listenEveryBeliever(){
synchronized (believers){
for(Believer be : believers){
be.confide();
}
}
} }

测试:

 @Test
public void test1(){
God god = new God();
god.addBeliever(new Believer() {
@Override
public void confide() {
System.out.println("I love my son....");
}
});
god.listenEveryBeliever();
}

I love my son....

没问题,再看下面:

@Test
public void test2(){
God god = new God(); god.addBeliever(new Believer() {
@Override
public void confide() {
System.out.println("I love my son....");
}
}); //现在出现一个亵神者,放弃信仰了
god.addBeliever(new Believer() {
@Override
public void confide() {
god.removeBeliever(this);
}
});
god.listenEveryBeliever();
}

结果,在上帝做倾听信徒时,居然出现了渎神者,大家都知道,集合在循环时,时不可以增减元素的。

Effective java 系列之避免过度同步和不要使用原生态类型,优先考虑泛型

在继续看,死锁了....

 @Test
public void test3(){
God god = new God(); god.addBeliever(new Believer() {
@Override
public void confide() {
System.out.println("I love my son....");
}
}); //现在出现一个亵神者,放弃信仰了
god.addBeliever(new Believer() {
@Override
public void confide() {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(()->{god.getBeliever(this);}).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executor.shutdown();
}
}
});
god.listenEveryBeliever();
}

这里死锁,分析下:god.listenEveryBeliever(),主线程锁住天堂,上帝开始倾听信徒倾述,但此时有一个信徒倾述时另启一个线程,并在此等待主线程执行结束,当然永远也等不到....

所以正确的姿势:原则:不要在同步块中,调用可能被覆盖的方法

 /**
* 倾听每位信徒倾述
*/
public void listenEveryBeliever(){ List<Believer> believersBat = null;
synchronized (believers){
//快照副本
believersBat = Lists.newArrayList(believers);
}
for(Believer be : believersBat){
be.confide();
}
}

请不要在新代码中使用原生态类型(23)

jdk1.5版本增加了泛型,如果还是再用低于1.5的jdk,不说了。

泛型的好处:1、表述性 2、安全性(主要体现在编译阶段的泛型检查,有些错误不必要等到运行时才发现)

    @Test
public void test1(){
//运行时才发现类型异常,追悔吧...
List list = Lists.newArrayList("adc");
getFirstEle1(list);//java.lang.String cannot be cast to java.lang.Integer
} @Test
public void test2(){
List list = Lists.newArrayList("adc");
getFirstEle2(list);//adc
} @Test
public void test3(){
//结合test2() List和List<Object>主要区别:
//1、List 时原生态类型,逃避了类型检查。List<Object>则明确告知编译器
//2、List<String> 时List的字类,而不是List<Object>子类
List<String> list = Lists.newArrayList("adc");
//getFirstEle2(list);//编译报错
} @Test
public void test4(){
List<String> list = Lists.newArrayList("adc");
getFirstEle3(list);//adc
} @Test
public void test5(){
List<? extends String> list1 = Lists.newArrayList("adc");
List<String> list2 = Lists.newArrayList("ap"); //<? extends E> 是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限
//list2 元素类型String,list1元素类型继承String,大类型->小类型,报错
//list1.addAll(list2);
list2.addAll(list1);
System.out.println(list2); //[ap, adc]
} @Test
public void test6(){
// <? super E> 是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限
List<? super String> list1 = Lists.newArrayList("adc");
List<String> list2 = Lists.newArrayList("ap");
list1.addAll(list2);
System.out.println(list1); //[ap, adc]
} /**
* List 原生态类型
* @param list
*/
public static void getFirstEle1(List list){
int i = (int) list.get(0);
System.out.println(i);
} /**
* List<Object> 从表述上,时所有Object元素都ok
* @param list
*/
public static void getFirstEle2(List<Object> list){
Object i = (Object) list.get(0);
System.out.println(i);
} /**
* <?> 可接受任意泛型,多定义在变量中
* @param list
*/
public static void getFirstEle3(List<?> list){
System.out.println(list.get(0));
} /**
* <T> 表示方法要用到泛型参数,多用在类和方法上
* @param list
* @param <T>
*/
public static <T> void getFirstEle4(List<T> list){
T t = list.get(0);
System.out.println(t);
}

私有构造器强化不可实例化

使用场景:通常用在工具类,因为他们都不希望被实例化。

public class MyUtils {
    private MyUtils(){
        //不管外部还是内部,该类实例化就会抛异常
        throw new AssertionError();
    }
    
    public static String method(){
        return "";
    }
}  

枚举类型:

通常程序中需要用到大量的常量,优雅的使用枚举将对代码可读性、健壮性、维护性有显著效果

enum代替int常量,用实例域代替序数

package com.example.demo.effective.enumDemo;

public enum StatusEnum {
APPLY(1),ACTIVE(2),FREEZE(3),FREE(4); private final int index; StatusEnum(int size){
this.index = size;
} public void method(){
//System.out.println(this.ordinal()); 位置顺序变动,会乱套
System.out.println(index);
}
}

用EnumSet代替位域

// num << x,相当于num * 2^x,
//|或运算符:即两个二进制数同位中,只要有一个为1则结果为1,若两个都为1其结果也为1
public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUGH = 1 << 3;
@Test
public void test3(){
System.out.println(STYLE_UNDERLINE|STYLE_STRIKETHROUGH);//
} @Test
public void test4(){
//用EnumSet代替位域
//位域表示法允许利用位操作,有效地执行先 union(联合)和 intersection(交集)这样的集合操作 如:test3()
EnumSet<StatusEnum> status1 = EnumSet.of(StatusEnum.APPLY, StatusEnum.ACTIVE);
System.out.println(status1); //确定,EnumSet可变,用unmodifiableSet 封装
Set<StatusEnum> unmodifiableStyle = Collections.unmodifiableSet(status1);
unmodifiableStyle.add(StatusEnum.FREE); //运行时:UnsupportedOperationException
}