Effective Java ReadingNotes 9

Effective Java阅读笔记(9)

Effective Java第10章 并发

66.同步访问共享的可变数据

当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。

67.避免过度同步

为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法,并要尽量限制同步区域内部的工作量。

68.executor和task优先于线程

现在应当优先使用executor和task而不是Thread,因为在以前Thread既是工作单元又是执行单元,但是现在二者分离,executor是执行单元,Runnable和Callable是工作单元。

使用ScheduledThreadPool代替Timer作为多线程的定时器。

对于ThreadPool:

  1. 使用Executor.newCachedThreadPool来应对一个轻负载的服务器。
  2. 使用Executor.newFixedThreadPool来应对一个重负载的服务器。
  3. 为了最大限度控制它,直接使用ThreadPoolExecutor。
69.并发工具优先于wait和notify

考虑使用java.util.concurrent而非wait和notify。如果你在维护使用wait和notify的代码,务必确保始终是利用标准的模式从while循环内部调用wait。一般情况下,你应该优先使用notifyAll,而不是使用notify。如果使用notify,请一定要小心,以确保程序的活性。

70.线程安全性的文档化

线程安全性的几种级别:

  1. 不可变的immutable,这个类的实例是不变的,所以不需要外部的同步。例如:String、Long和BigInteger。

  2. 无条件的线程安全unconditionally thread-safe,这个类的实例是可变的,但是这个类有着足够的内部同步,所以它的实例可以被并发使用,无需任何外部同步。例如:Random和ConcurrentHashMap。

  3. 有条件的线程安全conditionally thread-safe,除了有些方法为进行安全的并发而使用需要外部同步之外,这种线程安全级别与无条件的线程安全相同。例如:Collections.synchronized包装返回的集合,它们的迭代器iterator要求外部同步。

  4. 非线程安全not thread-safe,这个实例是可变的。为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法调用(或者调用序列)。例如:ArrayList和HashMap。

  5. 线程对立的thread-hostie,这个类不能安全地被多个线程并发使用,即使所有的方法调用都被外部同步包围。

如果编写无条件的线程安全类,建议使用私有锁,隐藏内部实现,私有锁尤其适合于专门为继承而设计的类。如果编写有条件的线程安全类,一定要在API文档中注明哪个方法调用序列需要外部同步,以及执行这些序列需要哪把锁。

71.慎用延迟初始化

除非绝对必要,否则就不要这么做。如果域只在类的实例部分被访问,并且初始化这个域的开销很高,可能就值得进行延迟初始化。在大多数情况下,正常的初始化要优先于延迟初始化。

四种延迟初始化的方式:

1.最普通的:

private FieldType field;
synchronized Field getField(){
    if(field==null){
        field=computeFieldValue();
    }
    return field;
}

2.对静态域的出于性能考虑的初始化方式:

private static class FieldHolder{
    static final FieldType field=computeFieldValue();
}
static FieldType getField(){ return FieldHolder.field; }

3.双重检查模式(出于性能考虑,避免初始化之后的锁定访问的开销):

private volatile FieldType field;
Field getField(){
    FieldType result=field;
    if(result==null){
        synchronized(this){
            result=field;
            if(result==null){
                field=result=computeFieldValue();
            }
        }
    }
    return result;
}

4.单重检查模式(双重检查模式的变式,可以接受重复初始化):

private volatile FieldType field; 
private FieldType getField(){ 
    FieldType result=field; 
    if(result==null){ 
        field=result=computeFieldValue(); 
    } 
    return result; 
}
72.不要依赖于线程调度器

线程只做有意义的工作,不应该忙等。

不要依赖Thread.yield改变优先级,因为它在不同环境下效果不同,而且实现也不同。对于大多数程序员来说,Thread.yield的唯一用途是在测试期间人为地增加程序的并发性。

不要使用调整线程优先级的方法,它不具有可移植性。

线程优先级可以提高一个已经能够正常工作的程序的服务质量,但永远不应该用来“修正”一个本来不能工作的程序。

要编写健壮的、相应良好的、可移植的多线程应用程序,最好的办法是确保可运行线程的平均数量不明显多于处理器的数量,这使得线程调度器没有更多的选择:它只需要运行这些可运行的线程,直到它们不再可运行。在等待的线程并不是可运行的。

73.避免使用线程组

ThreadGroup API已经过时。

Gaoge

A student.

Beijing, China ggao.me