您的位置:首页 > Web前端

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

2016-05-22 17:33 281 查看

前言

以前当我在网络上看到爆发语言大战时,也会在一旁“看戏”,这个语言怎样怎样,那个语言怎样怎样,感觉挺有趣的,我对其中的褒贬也会半信半疑。后来我慢慢对这些言论不再感兴趣,基本上看到就会跳过,除非是对多门语言都非常熟悉的人发表的,比如说某知乎大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
组合来实现。最常用的同步器是
CountDownLatch
Semaphore
,比较不常用的是
CyclicBarrier
Exchanger
。虽然始终应该优先使用并发工具,但是还是要明白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,你可以忽略掉它们,就当根本不存在一样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: