`
walksing
  • 浏览: 212693 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

中等规模的并发程序设计 温绍锦

阅读更多
中等规模的并发程序设计 温绍锦
多核时代来临
硬件的多核时代已经来临
2007 年 4 核, 2008 年 8 核, 2010 年 16 核, 2011 年 32 核
硬件促使软件发生变化
更好的并发程序设计框架 ( executor 、 future 、 blocking queue )
更易用的算法、数据结构 ( lock-free data structure )
并发程序设计将会变得简单而且逐步普及,按 Herb Sutter 的预测,这个过程为 2007 年 ~2012 年
我们幸运处于这个变革中
旧时王谢堂前燕,飞入寻常百姓家


线程(一)
用户线程、内核线程、轻量级进程
三种映射模型
1:1 、 N:1 、 M:N 三种映射模型
Java 线程在 Windows 和内核线程一对一影射( _beginthread )
Java 在 Linux 下使用 pthread 实现。 Phtread 在 linux 下有两种实现, linux 2.6 内核缺省使用 NPTL 。也是一对一影射。
Java 在 Solaris 下使用多对多映射。( Solaris 、 HP-UX 、 True64 UNIX 等操作系统本身支持 M:N 模型)
线程(二)
操作系统线程标准
POSIX Thread ( PThread )
这是应用最广泛的线程库,在各个版本的 unix 、 linux 下均有实现。 Linux 早期的版本并不完全符合 PThread 标准, Linux 2.6 内核缺省集成 NPTL ,完全符合 PThread 标准。
Win32 和 OS/2 Threads
Win32 Threads 提供大量线程通讯 API ,功能强大。
Solaris Threads
DCE Threads
同步原语(一)
线程特定存储
ThreadLocal ,又称为 TLS 、 TSS 、 TSD 等,在各文档中名称混乱。每个线程有唯一的标识,关联线程相关的特定数据,就是线程特定存储。
Monitor
中文翻译为管程。 synchronized 、 Object.wait() 、 Object.notify() 的底层实现。但是 java 原生提供的 monitor 不完整,因为没有提供 Condition , Object 本身隐含提供一个 Condition 。
同步原语(二)
条件变量 ( Condition )
这是并发基本概念,在各种线程库中均有提供。 Object 对象隐含一个条件变量。
条件变量都提供三个原子操作 wait 、 signal 、 broadcast 。在 java.util.concurrent.Condition 中,对应的是 await 、 signal 、 signalAll
wait 必须在 signal 之前,否则会导致信号丢失的问题
同步原语(三)
上下文切换
保存和恢复寄存器状态
保存和恢复线程 Cache 状态的开销。各线程在运行时彼此淘汰对方的 cache 数据。( CPU 的缓存)
线程太多,会导致频繁的上下文切换,导致过多无谓的开销。
在阻塞等待的线程不参与调度,不会导致上下文切换
减少上下文开销的一个办法就是使用用户线程(纯 java 无法实现)。 Oracle 、 MS SQL Server 都是用用户线程,并宣称从用户线程中获得更好的性能。
同步原语(四)
死锁
ABA 问题容易导致死锁
锁的粒度大,竞争严重,锁的粒度小,容易死锁,乃取舍问题
注意锁的顺序
需要考虑外部的锁,例如数据库的锁
死锁的解决办法
通过协议预防死锁的发生
允许系统进入死锁状态,然后检测恢复
忽视它的存在
分解
任务分解
不同的程序行为采用不同的线程执行
数据分解
多个程序对不同的数据块执行相同的操作
数据流分解
一个线程的输出作为另外一个线程的输入
Java 传统的并发程序设计
使用原生的 Java 并发支持, wait() 、 notify() 、 synchronized
很难使用,容易导致程序结构混乱。
这些都是太低级别的 API ,容易导致性能问题
重新发明轮子太多 。(经常需要发明类似 BlockingQueue 、 ReadWriteLock 之类的轮子)
java.uti.concurrent 的目标
并发程序设计简单化
提供基本的并发类
包括 Lock 、 Condition 、 Semaphore 、 Atomic 等
提供线程安全的数据结构
ConcurrentLinkedQueue 、 ConcurrentHashMap
提供了一些常用的工具类
Thread pool 、 Scheduler 、 barrier 、 latch 、 blocking queue 等
提供 Atomic
为专家提供用于开发高级的 lock-free 算法
拜神 Doug Lea Doug Lea - Mr. concurrency ,当今世界上并发程序设计领域的先驱,著名学者。他是 util.concurrent 包的作者, JSR166 规范的制定。图书著作《 Concurrent Programming in Java: Design Principles and Patterns 》。其” A Scalable Elimination-based Exchange Channel” 和” Scalable Synchronous Queues” 两篇论文列为非阻塞同步算法的经典文章
Executors (一)
这是一种任务分解。任务提供者和执行者
在本线程内完成,或者交给专门的 Executor 去执行。
Executor 可以分为多种,或者允许指定运行策略
任务提供者和执行者之间需要一种通讯机制,用于:
等到任务执行结束(或者等待一段时间)
取消任务
等待时执行错误通知
获取执行结果
这种机制成为 Future ,这是一个很关键的概念,在并发程序中使用,能使程序清晰化,而且功能完备。在各种并发的库中均有提供类似的概念。
Executors ( 二 )
Executor 用于执行所提交的任务
interface Executor { void execute(Runnable command); }
ExecutorService 生命周期管理,提供了 Future 返回和其他工具方法
interface ExecutorService extends Executor { void shutdown(); Future<?> submit(Runnable task); }
Executors 提供静态工厂方法
class Executors { ExecutorService newFixedThreadPool(int nThreads){…} ExecutorService newCachedThreadPool() {…} // … many more…
}
Future ( 一 )
一种很常用并发程序设计的手段便是任务分解。 Task Provider 分解任务,提交给 Executor 执行。 Task Provider 和 Executor 之间需要一种通讯手段,这种手段的具体实现,通常叫做 Future 。 Future 通常包括 get (阻塞至任务完成), cancel , get(timeout) (等待一段时间)等等。 Futue 也用于异步变同步的场景。
Future (二)
Future 的接口
class Future<V> { boolean isDone(); V get(); V get( long timeout, TimeUnit unit); void set(V v) ; void setException(Throwable t); }
Future (三)
Future 已经是一个很流行的线程间通讯工具类,在很多网络并发的库中均有使用,例如 C++ 网络库 ACE , apache 的 nio 框架 mina , Visual C++ 内置 future 关键字直接使用。
Future 是一个很见的工具类,但他能够使得并发程序代码结构变动更清晰,使用灵活,可以以此发挥,创造高级的设计模式
不会使用 Future 的人,有点土。。。
BlockingQueue (一)
常用的工具类,用于数据流分解
读取阻塞,插入阻塞(可选)
ArrayBlockingQueue
FIFO ,有上限
LinkedBlockingQueue
FIFO ,可能有上限
PriorityBlockingQueue
按优先次序,无上限
SynchronousQueue
Rendezvous channel
BlockingQueue (二)
interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
E peek();
E take() throws InterruptedException;
E poll( long timeout, TimeUnit unit)
throws InterruptedException;
int drainTo(Collection<? super E> c);
}
BlockingQueue (三)
注意 add 、 offer 、 put 的语义差别,养成使用 offer 的良好习惯
注意 peek 、 take 、 poll 的语义差别,不要搞混了 peek 和 take 。
必要时候使用 drainTo 提高性能
定时器 (一)
传统的 java.util.Timer
每个 Timer 启动一个 TimerThread
没有直接和线程池或者 Executor 结合
内部的 TimerQueue 性能不够好,也不适用于较大规模的 Timer 处理
精度只能到达毫秒级
定时器 (二)
ScheduledExecutorService
extends ExecutorService ,利用线程池执行调度任务
内部使用 DelayQueue 实现(一个阻塞队列和优先队列的结合)
interface ScheduledExecutorService extends ExecutorService { ScheduledFuture <?> schedule(Runnable command, …); ScheduledFuture <?> scheduleAtFixedRate(Runnable command, …) ScheduledFuture <?> scheduleWithFixedDelay(Runnable command }
更高的精度
返回提供了 ScheduledFuture ,控制更灵活
建议使用 ScheduledExecutorService 替代 Timer
定时器(三)
使用 DelayQueue
使用于以下场景
关闭空闲连接
清空缓存中的 Item
任务超时处理
替代笨笨的后台线程定时挨个检查的方式
DelayQueue 是一个使用优先队列( PriorityQueue )实现的 BlockingQueue ,优先队列的比较基准值是时间。
BlockingQueue + PriorityQueue + Delayed
interface  Delayed  extends Comparable<Delayed> {     long getDelay(TimeUnit unit); }
class  DelayQueue <E extends Delayed>  implements  BlockingQueue <E> {      private final  PriorityQueue <E> q; }
定时器(四)
大规模定时器的实现
在操作系统,网络应用,或者服务器端,可能存在大规模的定时器,提交的定时调度任务上万个,中途随时取消已提交的任务。
Java 内置的两个定时器实现 Timer 和 ScheduledThreadPoolExecutor 都不能满足大规模定时器的性能需求。 Timer 在操作时需要锁住整个 TimerQueue , ScheduledThreadPoolExecutor 内部使用 DelayQueue ,操作时也需要锁住整个优先队列,插入时还需要排序。
存在一种算法 TimerWheel ,适用于大规模的定时器实现。这个算法最早是被设计用来实现 BSD 内核中定时器的,后来被广泛移植到诸如 ACE 等框架中,堪称 BSD 中经典算法之一,能针对定时器的各类常见操作提供接近常数时间的响应,且能根据需要很容易进行扩展。( TimerWheel 在 Java 5 中实现很简单)
锁 (一)
使用 synchronized 关键字同步 public synchronized void f() {} synchronized(this) {}
使用简单
性能低下。比使用 ReentrantLock 性能要低得多
调试监控不方便,无法得知“锁”拥有者线程
锁(二)
Lock 更加灵活,性能更好 interface Lock {
void lock ();
void lockInterruptibly () throws InterruptedException;
boolean tryLock ();
boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException;
void unlock ();
Condition newCondition ();
}
支持多个 Condition
可以不以代码块的方式上锁
可以使用 tryLock ,并指定等待上锁的超时时间
调试时可以看到内部的 owner thread ,方便排除死锁
RenntrantLock 支持 fair 和 unfair 两种模式
锁(三)
ReadWriteLock
允许多个读,一个写
Spin-Lock
Atomic (一)
基于 CPU 硬件提供的 TSL 指令
基本思想是 compare and set ( CAS )作为原子操作
通常和一个忙等待结合使用, CAS 操作时需要一个回退机制
非阻塞算法的基础
Lock-free 数据结构以此作为基础,获得更好的并发性能。
Atomic (二)
class AtomicInteger extends Number {
int get() {}
int incrementAndGet() {}
int decrementAndGet() {}
boolean compareAndSet( int expect, int update) {…}
}
Atomic (三)
AtomicInteger
用于计数器非常恰当
AtomicReference
在 lock-free 的数据结构中非常有用
Atomic (四)
幕后的非阻塞算法
如果深入 JVM 和操作系统,会发现非阻塞算法无处不在。垃圾收集器使用非阻塞算法加快并发和平行的垃圾搜集;调度器使用非阻塞算法有效地调度线程和进程,实现内在锁。在 Mustang ( Java 6.0 )中,基于锁的 SynchronousQueue 算法被新的非阻塞版本代替。
性能比较(一)
8-way Ultrasparc3 中同步、 ReentrantLock 、公平 Lock 和 AtomicLong 的基准吞吐量
性能比较(二)
单处理器 Pentium 4 中的同步、 ReentrantLock 、公平 Lock 和 AtomicLong 的基准吞吐量
性能比较(三)
结论
直接使用 Atomic 在单 CPU 下性能最好
在多 CPU 情况下, ReentrantLock 性能最好
Synchronized 和 fair ReentrantLock 性能都较差
Lock-free 数据结构 (一)
提供 compare and set 操作
使用者不需要锁
并发性能更好
在并发情况下,更容易使用,不容易出错
Lock-free 数据结构 (二)
class ConcurrentHashMap<K, V> {
V putIfAbsent(K key, V value) {}
boolean remove(K key, V value);
boolean replace(K key, V oldValue, V newValue) ;
}
class ConcurrentLinkedQueue<E> extends AbstractQueue<E> {
}
CopyOnWriteArrayList
COW 是一个很古老的算法
常用于 event listener 的管理
Exchanger
是结合数据分解和数据流分解的一种技巧
Java SE 5只支持2 parities , Java 6 支持 N parities 。
并发流程控制
Semaphore
Latch
CountDownLatch
Barrier
CyclicBarrier
这些工具类都很简单,属于并发流程控制的典型手段
参考资料
Java SE 6.0 源码
《操作系统概念》第六版
《现代操作系统》
《多核程序设计技术》
《 pthread primer 》
http://www.cs.wisc.edu/trans-memory/biblio/swnbs.html
姚继锋的文章“ Linux 下的多线程编程 ”
Flier_Lu 的博客 http://www.blogcn.com/User8/flier_lu/index.html
温少的博客
http://www.cnblogs.com/jobs


事件处理模式
Ractor
Proactor
ACT
Connector 、 Acceptor
Leader/Follow
Half sync/half async, halft async/ half sync
(内容较多,应该专门一个课程讲解)
一些资源
NPTL (Native Posix Thread Library)
http://people.redhat.com/drepper/nptl-design.pdf
Threads Newsgroup
http://www.lambdacs.com/cpt/FAQ.html
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics