Effective Java读书笔记(四):并发

来源:互联网 发布:淘宝的延长收货是几天 编辑:程序博客网 时间:2024/04/30 07:15

前言

以前当我在网络上看到爆发语言大战时,也会在一旁“看戏”,这个语言怎样怎样,那个语言怎样怎样,感觉挺有趣的,我对其中的褒贬也会半信半疑。后来我慢慢对这些言论不再感兴趣,基本上看到就会跳过,除非是对多门语言都非常熟悉的人发表的,比如说某知乎大v曾经写的Java sucks,C# rocks文章,这样的文章才更值得一读。这样的想法转变是因为,一般而言当我对任何东西更加熟悉时,我才会更加知道原来自己不懂的东西还有更多。
今天总结的是《Effective Java》里并发这个专题。


并发

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

这一小节就是试图让我们弄清楚Java:线程同步的一些概念,当搞清楚这些概念后,才能明白怎样正确的处理同步。
当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步,如果没有同步,就不能保证一个线程所做的修改,可以被另外一个线程获知。

2. 避免过度同步

这一节内容有点深,暂时还不能消化不下来……
过度同步可能会导致性能降低、死锁、甚至不确定的行为。通常,你应该在同步区域内尽量不要调用外来方法,并且尽量限制同步区域的工作量,获得锁,处理共享数据,释放锁。

3. executor和task优先线程

这一小节简单介绍了下线程池Java:线程池基础,还有Executor Framework的一些功能,比如Executor Framework能非常简单地完成以下工作:

  • 等待一个任务集合中的任何任务或者所有任务完成(ExecutorService#invokeAny/All方法)
  • 优雅地中断或终止任务(shutdown/shutdownNow/awaitTermination)
  • 任务完成时逐个获取这些任务结果(ExecutorCompletionService)

总而言之,作者提醒我们,要优先使用Executor Framework而非直接使用线程来处理任务和实现并发,至于怎么使用文章并未讨论太多,这个需要读者专门学习。

4. 并发工具优先于wait和notify

Java1.5发行版本中,添加了一个并发工具包java.util.concurrent,其中就包括第3点的Executor Framework,另外还包括并发集合以及同步器
并发集合为标准的集合接口(List、Queue、Map)提供了高性能的并发实现。为了提供高并发性,这些实现在内部自己管理同步。比如ConcurrentHashMap以及各种BlockingQueue实现。
同步器是一些使线程能够等待另一个线程的对象,允许他们协调动作,没有同步器之前,过去常常使用wait/notify组合来实现。最常用的同步器是CountDownLatchSemaphore,比较不常用的是CyclicBarrierExchanger。虽然始终应该优先使用并发工具,但是还是要明白wait方法的标准模式以维护遗留代码:

Synchronized(obj){    while(condition does not hold)        obj.wait();}

与第3节一样,作者只是提醒我们应该使用并发工具,至于怎么使用文章并未讨论太多,这个需要读者专门学习,《Java编程思想》里面有一个节专门就是将同步器的,还有一些并发集合的。

5. 线程安全性的文档

总而言之,必要的时候(文中写的是每个类都应该,但是现实中几乎不太可能),必须清楚地在文档中说明它的线程安全属性。集中常见的情形如下:

  • 不可变的:类实例不可变,所以不需要同步,比如String
  • 无条件的线程安全:类的内部已经做好同步,它的实例可以被并发使用,无需任何外部同步,比如ConcurrentHashMap
  • 有条件的线程安全:有些方法需要外部同步,比如Conllections.synchronized包装返回的集合,它的的迭代器(iterator)要求外部同步。
  • 非线程安全:并发时调用这些实例的方法时必须实施同步,比如ArrayList和HashMap
  • 线程对立的:不能并发使用,JDK中这样的类非常少~

上述几种情况也可以概括为:不可变、线程安全、线程不安全三种情况。

6. 慎用延迟初始化

如果域只有在类的实例部分被访问,并且初始化的开销很高,才值得进行延迟初始化。否则大多数的域都应该正常地初始化,而不是延迟初始化。假设真的有必要进行,也还应该注意线程同步的问题:

  • 对于实例域,使用双重检查模式
private volatile Instance ins;public Instance getInstance(){    if(ins == null){        synchronized(this){            if(ins == null)                ins = new Instance();        }    }    return ins;}
  • 对于静态域,使用内部类(实例域也可以用这个方法实现单例)。
private static Instance ins;private static class InstanceHolder{    static final Instance INS = new Instance();}public static Instance getInstance(){    return InstanceHolder.INS;}
  • 可以接受重复初始化的
private Instance ins;public Instance getInstance(){    if(ins == null)        ins = new Instance();    return ins;}

7. 不要依赖于线程调度器

不要让程序的正确性依赖于线程调度器,比如常见的是调用Thread.yield方法以及设置线程优先级,这些措施仅仅对调度器做些暗示,没有任何机制保证它将会被采纳。

8. 避免使用线程组

简而言之线程组有多bug,你可以忽略掉它们,就当根本不存在一样。

0 0