1.并发的多面性
用并发解决的问题大体上可以分为 “速度” 和 “设计可管理性” 两种。
实现并发最直接的方式是在操作系统级别使用进程。
1.1 更快的执行
些编程语言被设计为可以将并发任务彼此隔离,这些语言通常被称为函数型语言,其中每个函数调用都不会产生任何副作用(并因此而不能干涉其他函数),井因此可以当作独立的任务来驱动。Erlang就是这样的语言,它包含针对任务之间彼此通信的安全机制。
Java采取了更加传统的方式,在顺序型语言的基础上提供对线程的支持,与在多任务操作系统中分叉外部进程不同,线程机制是在由执行程序表示的单一进程中创建任务。这种方式产生的一个好处是操作系统的透明性,这对Java而言,是一个重要的设计目标。
1.2 改进代码设计
并发需要付出代价,包含复杂性代价,但是这些代价与在程序设计、资源负载均衡以及用 户方便使用方面的改进相比,就显得微不足道了。通常,线程使你能够创建更加松散耦合的设 计,否则,你的代码中各个部分都必须显式地关注那些通常可以由线程来处理的任务。
2.基本的线程机制
并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(也被称为子任务)中的每一个都将由执行线程来驱动。
3.共享受限资源
3.1 第一种方式——序列化访问
基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。这意味着在给定时刻只允许一个任务访间共享资源。
- synchronized
- 显式锁Lock
- 原子性与易变性
- 原子类
- 临界区
- 在其他对象上同步
3.2 第二种方式——根除对变量的共享
- ThreadLocal
4.终结任务
5.线程之间的协作
共享资源的互斥访问
当你使用线程来同时运行多个任务时,可以通过使用锁(互斥)来同步两个任务的行为,从而使得一个任务不会于涉另个任务的资源。也就是说,如果两个任务在交替着步入某项共享资源(通常是内存),你可以使用互斥来使得任何时刻只有一个任务可以访问这项资源。任务协作
学习如何使任务彼此之间可以协作,以使得多个任务可以一起工作去解决某个问题。现在的问题不是彼此之间的干涉,而是彼此之间的协调,因为在这类问题中, 某些部分必须在其他部分被解决之前解决。
当任务协作时,关键问题是这些任务之间的握手。为了实现这种握手,我们使用了相同的基础特性:互斥。 在这种情况下, 互斥能够确保只有一个任务可以响应某个信号,这样就可以根除任何可能的竞争条件。在互斥之上,我们为任务添加了一种途径,可以将其自身挂起,直至某些外部条件发生变化(例如,管道现在已经到位),表示是时候让这个任务向前开动了为止。
5.1 wait()与notifyAll()
5.2 notify()与notifyAII()
5.3 生产者和消费者
5.4 生产者-消费者与队列
5.5 任务间使用管道进行输入/输出
6.死锁
- 哲学家就餐
- 死锁的四个条件
7.并发工具类
- CountDownLatch
- CyclicBarrier
- DelayQueue
- PriorityBlockingQueue
- ScheduledExecutor
- Semaphore
- Exchanger
8.仿真
9.性能调优
9.1 比较各类互斥技术
比较这些不同的方式,更多地理解它们各自的价值和适用范围。
- synchronized关键字
- Lock类
- Atomic类
9.2 并发容器
ConcurrentHashMap和ConcurrentLinkedQueue
- 乐观锁
- 比较各种Map实现
9.3 乐观加锁
9.4 ReadWriteLock
10.活动对象
有一种可替换的方式被称为活动对象或行动者。之所以称这些对象是“活动的",是因为每个对象都维护着它自己的工作器线程和消息队列,并且所有对这种对象的请求都将进人队列排队,任何时刻都只能运行其中的一个。因此,有了活动对象,我们就可以串行化消息而不是方法,这意味着不再需要防备一个任务在其循环的中间被中断这种问题了。
当你向一个活动对象发送消息时,这条消息会转变为一个任务,该任务会被插入到这个对
象的队列中,等待在以后的某个时刻运行。Future在实现这种模式时将派上用场。
11.总结
明白什么时候应该使用并发、 什么时候应该避免使用井发是非常关键的。 使用它的原因主要是:
- 要处理很多任务,它们交织在一起,应用并发能够更有效地使用计算机(包括在多个CPU上透明地分配任务的能力)。
- 要能够更好地组织代码。
- 要更便于用户使用。
线程的一个额外好处是它们提供了轻量级的执行上下文切换(大约100条指令),而不是重最级的进程上下文切换(要上千条指令)。 因为一个给定进程内的所有线程共享相同的内存空间,轻量级的上下文切换只是改变了程序的执行序列和局部变量。 进程切换(重量级的上下文切换)必须改变所有内存空间。
多线程的主要缺陷有:
- 1)等待共享资源的时候性能降低。
- 2)需要处理线程的额外CPU花费。
- 3)糟糕的程序设计导致不必要的复杂度。
- 4)有可能产生一些病态行为,如饿死、 竞争、 死锁和活锁(多个运行各自任务的线程使得
整体无法完成)。 - 5)不同平台导致的不一致性。 比如,竞争条件在某些机器上很快出现,但在别的机器上根本不出现。 如果你在后一种机器上做开发,那么当你发布程序的时候就要大吃一惊了。