并发编程20190725

Younghou

2019/07/25 发布于 科学 分类

并发编程Go/Python/Go

并发编程  Go  python  java 

文字内容
1. 并发编程 简单的使⽤用交流 侯惠阳 2019/07/20 Houhuiyang
2. ⼤大纲 • 同步、异步 • 阻塞、⾮非租塞(BIO、NIO、AIO) • 并发、并⾏行行 • 进程、线程、协程 • Java的并发编程 • Go的并发编程 • Python的并发编程(2.x/3.x) • 并发模型 • • 多进程和多线程并发编程模型 • 异步响应编程模型 • Future (Java) • CallBack、Promise(Java) • Actor编程模型 • CSP编程模型(gmp) 并发设计模式(Immutability、Copy-On-Write、ThreadLocal、Guarded Suspension、Balking、 Thread-Per-Message、Worker Thread、2pc、Producer-Consumer) • 参考⽂文献 2
3. 同步、异步 • 同步(synchronous):发送⼀一个请求,等待返回,然后再发送下⼀一个请求。提交请 求 -> 等待服务器器处理理 -> 处理理完返回,此期间客户端浏览器器不不能⼲干任何事; • 异步(asynchronous):发送⼀一个请求,不不等待返回,随时可以再发送下⼀一个请求。 提交请求 -> 服务器器处理理(这时浏览器器仍然可以作其他事情)-> 处理理完毕; • 同步可以保证顺序⼀一致,但是容易易导致阻塞; • 异步可以解决阻塞问题,但是会改变顺序性; 3
4. 阻塞、⾮非阻塞 • 阻塞:不不能⽴立即获得返回,需要等待;也就是说阻塞调⽤用是指调⽤用结果返回之前,当 前线程会被挂起。调⽤用线程只有在得到结果之后才会返回; • ⾮非阻塞:⽴立即获得返回,不不需要等待;⾮非阻塞调⽤用指在不不能⽴立刻得到结果之前,该调 ⽤用不不会阻塞当前线程; 类别 示例例 备注 I/O框架 客户端:线程数 同步阻塞 在银⾏行行排队,不不 ⼲干别的事情 效率最低 BIO 1:1 同步⾮非阻塞 排队时,边打电话 边抬头看是否轮已 到⾃自⼰己 效率低下 NIO M:N 异步阻塞 在银⾏行行领⼀一个号, 在银⾏行行⾥里里等,不不能 做别的事情 异步⾮非阻塞 领完号码,在忙⾃自 ⼰己的事情,等柜台 同通知 M:1 效率最⾼高 AIO M:0 4
5. BIO、NIO、AIO 5
6. 并发、并⾏行行 • 并发(concurrency):同⼀一时间段处理理多件事情(⼀一个⼈人同时吃三个馒头); • 并⾏行行(parallelism):同⼀一时间处理理多个任务(三个⼈人同时吃三个馒头); • “并⾏行行”概念是“并发”概念的⼀一个⼦子集。也就是说,你可以编写⼀一个拥有多个线程或者进 程的并发程序,但如果没有多核处理理器器来执⾏行行这个程序,那么就不不能以并⾏行行⽅方式来运 ⾏行行代码。因此,凡是在求解单个问题时涉及多个执⾏行行流程的编程模式或者执⾏行行⾏行行为, 都属于并发编程的范畴; 6
7. 并⾏行行的2个定律律 • Amdahl定律律:定义了了串串⾏行行系统并⾏行行化后 的加速⽐比的计算公式和理理论上限(加速⽐比 • Gustafson定律律:说明处理理器器个数,串串⾏行行 ⽐比例例和加速⽐比之间的关系; =优化前系统耗时/优化后系统耗时) • ⼀一个程序(或者算法)可以按照是否可以 被并⾏行行化 • 可以被并⾏行行化的部分 • 不不可以被并⾏行行化的部分 7
8. 进程、线程、协程 • • • 进程:系统分配的最⼩小单位(多进程,⼀一个挂了了,另⼀一个不不影响) • text region:存储处理理器器执⾏行行的代码; • data region:存储变量量、进程执⾏行行期间的动态分配的内存; • stack region:存储活动过程中调⽤用的指令和本地变量量; • 状态(等待 -> 就绪 -> 运⾏行行) 线程(多线程是不不安全的,⼀一个线程挂了了,导致整个进程也崩溃了了) • 线程属于进程; • 线程共享进程的内存地址和空间; • 状态(新⽣生 -> 可运⾏行行 -> 运⾏行行 -> 阻塞 -> 死亡) 协程 • 协程属于线程; • 协程没有线程的上下⽂文切换的消耗; • 协程是原⼦子的; 8
9. 进程、线程的切换 9
10. Reactor模式 Reactor模式的核⼼心⾃自然是Reactor类,register_handler() 和 remove_handler() 这两个⽅方法可以注册 和删除⼀一个事件处理理器器,handler_events() 是核⼼心⽅方法,也是Reactor模式的发动机,核⼼心逻辑是: ⾸首先通过同步事件多路路选择器器提供的select()⽅方法监听⽹网络事件,当有⽹网络事件就绪后,就遍历事件处理理器器 来处理理该⽹网络事件,由于⽹网络事件是源源不不断的,所以在主程序中启动Reactor模式,需要⽤用while(true) {} 的⽅方式调⽤用handler_events()⽅方法。 10
11. Java的并发编程 11
12. Java的并发编程-核⼼心问题 分⼯工 如何⾼高效地拆解任务并分配给线程 同步 线程之间如何协作 互斥 保证同⼀一时刻只允许⼀一个线程访问共享资源 12
13. Java的并发编程-Bug的源头 可⻅见性 1、⼀一个线程对共享变量量的修改, 另外⼀一个线程能够⽴立刻看到; 2、单核、多核 硬件⼯工程师 给软件⼯工程师挖坑; 原⼦子性 有序性 1、程序按照代码的先后顺序执⾏行行, 1、我们把⼀一个或者多个操作 编译器器为了了优化,有时候会改变程序 在 CPU 执⾏行行的过程中 中的先后顺序; 不不被中断的特性; 2、CPU能保证的原⼦子操作是 2、Java案例例:双重检查创建单例例对象; CPU指令级别的,⽽而不不是⾼高级 3、32位机器器上Long变量量加减存在隐患; 语⾔言的操作符,这是违背我们 直觉的地⽅方。因此,很多时候 我们需要在⾼高级语⾔言层⾯面保证 操作的原⼦子性; 13
14. Java的并发编程-Java内存模型 论⽂文:Time, Clocks, and the Ordering of Events in a Distributed System 不不是Java独有的,C也有,原始意义是 禁⽤用CPU缓存; Volatile 举例例:我们声明⼀一个Volatile变量量 volatile int x = 88, 它告诉编译器器,对这个变量量的读写,不不能使⽤用CPU缓存,必须从内存中读取与写⼊入。 Happens-Before规则,前⾯面⼀一个操作的结果对后续操作是可⻅见的; “⼼心理理感应”,远隔千⾥里里,⼀一个⼈人⼼心之所想,另⼀一个⼈人也都能看到; Happens-Before 约束了了编译器器的优化⾏行行为; 同步,对⼀一个锁的解锁 Happens-Before 于后续对这个锁的加锁; Final Synchronized final 修饰变量量时,初衷是告诉编译器器,这个变量量⽣生⽽而不不变,可劲⼉儿优化; 14
15. Java的并发编程-解决原⼦子性 源头 就是 线程 切换 “同⼀一时刻只有⼀一个线程执⾏行行” 这个条件很重要,我们称为“互斥”。 如果我们能够保证对共享变量量的修改是互斥的, 那就不不论单核、多核,都能保证原⼦子性了了。 可以⽤用⼀一把锁来保护多个资源,但是不不能⽤用多把锁来保护⼀一个资源。 受保护的资源和锁之间合理理的关联关系是 N:1的关系 15
16. Java坑之死锁 16
17. Java坑之死锁 17
18. Go的并发编程 18
19. Go的并发编程 • 遵循Happens-Before规则 • 单个gorotune:读写和代码中的顺序⼀一致,即编译器器和CPU对程序的优化必须以不不改 变语⾔言规范定义的⾏行行为为前提; • 多个gorotune:由于指令重拍和优化的存在,⼀一个gorotune中对变量量的操作顺序和另 ⼀一个观测到更更改的顺序可能是不不⼀一样的; • 19
20. Go的内存模型 单个gorotune内,Happens-Before就是程序代码的顺序 两个之前存在三种关系:happens-before、happen-concurrently、happens-after not happens-before != happens-after,还有可能是happen-concurrently Go内存模型:https://golang.org/ref/mem 20
21. Go的Channel 21
22. Python的并发编程 22
23. Python的并发编程 • fork、subprocess • • ⼀一个进程可以fork⼀一个⼦子进程,并让这个⼦子进程exec另外⼀一个程序; threading • Global Interpreter Lock (GIL) • IPC • • ThreadLocal multiprocessing • • IPC • Pipe • Queue • Shared memory • Manager • Value • Array Pool 23
24. 多进程(Python) 24
25. Python GIL • 如果是CPU密集型(科学计算),使⽤用多进程处理理需求的⽅方式较多; • 如果是IO密集型(⽂文件读取,爬⾍虫等)则可以使⽤用多线程去处理理; • 什什么是Python GIL • 禁⽌止并发执⾏行行,全局解释器器锁(GIL)确保只有⼀一个线程在运⾏行行; • CPython的产物; • ⽤用Multiprocesssing 代替 Thread 25
26. Python GIL • 这种模式在只有⼀一个CPU核⼼心的情况下毫⽆无问题。任何⼀一个线程被唤起时都能成功获 得到GIL(因为只有释放了了GIL才会引发线程调度)。但当CPU有多个核⼼心的时候,问 题就来了了。从伪代码可以看到,从release GIL到acquire GIL之间⼏几乎是没有间隙的。 所以当其他在其他核⼼心上的线程被唤醒时,⼤大部分情况下主线程已经⼜又再⼀一次获取到 GIL了了。这个时候被唤醒执⾏行行的线程只能⽩白⽩白的浪费CPU时间,看着另⼀一个线程拿着 GIL欢快的执⾏行行着。然后达到切换时间后进⼊入待调度状态,再被唤醒,再等待,以此往 复恶性循环; 26
27. Python GIL 27
28. 协程(Python) • Python2.* • yield • gevent • • • pip install gevent Python3.* • asynico + yield from (3.4) • asynico + await(3.5) 爬⾍虫 28
29. 协程(Python) • 协程就是⼀一种特殊的并发机制 • 进程拥有⾃自⼰己独⽴立的堆和栈,既不不共享堆,亦不不共享栈,进程由操作系统调度; • 线程拥有⾃自⼰己独⽴立的栈和共享的堆,共享堆,不不共享栈,线程亦由操作系统调度(标准线程 是的); • • 协程和线程⼀一样共享堆,不不共享栈,协程由程序员在协程的代码⾥里里显示调度; gevent • 优点 • 执⾏行行效率⾼高,⼦子程序切换⼏几乎没有开销,与多线程相⽐比,线程越多,协程性能越明显; • 不不需要多线程的锁机制,因为只有⼀一个线程,也不不存在同时写变量量冲突,在控制共享资 源时也不不需要加锁; • I/O 多路路复⽤用是在⼀一个进程内部处理理多个逻辑流程,不不⽤用进⾏行行进程切换,性能较⾼高,另 外流程间共享信息简单; • 协程有编程语⾔言提供,由程序员控制进⾏行行切换,所以没有线程安全问题,可以⽤用来处理理 状态机,并发请求等 IO 密集型任务; • 缺点 • 不不能利利⽤用 CPU 多核优势; • 程序流程被事件处理理切割成⼀一个个⼩小块,程序⽐比较复杂,难于理理解; 29
30. 协程(Python) 30
31. 多进程和多线程并发编程模型 • select • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) • poll • • • int poll (struct pollfd *fds, unsigned int nfds, int timeout) epoll • int epoll_create(int size);//创建⼀一个epoll的句句柄,size:内核这监听的数量量 • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); libevent • 事件驱动,⾼高性能 • 轻量量级,专注于⽹网络 • 跨平台,⽀支持 Windows、Linux、Mac Os等 • ⽀支持多种 I/O多路路复⽤用技术, epoll、poll、dev/poll、select 和kqueue 等 • ⽀支持 I/O,定时器器和信号等事件 31
32. CallBack编程模型 32
33. Future模式 33
34. Callback(Netty-Future)模式 34
35. Actor编程模型 • Actor模型最早是1973年年Carl Hewitt、Peter Bishop和Richard Seiger的论⽂文中出现 的,受物理理学中的⼴广义相对论(general relativity)和量量⼦子⼒力力学(quantum mechanics)所启发,为解决并发计算的⼀一个数学模型; • Actor模型所推崇的哲学是”⼀一切皆是Actor“,这与⾯面向对象编程的”⼀一切皆是对象“类似; • Actor 的特点 • • 万物皆是Actor; • Actor之间完全独⽴立,只允许消息传递,不不允许其他“任何”共享; • 每个Actor最多同时只能进⾏行行⼀一样⼯工作; • 每个Actor都有⼀一个专属的命名Mailbox(⾮非匿匿名); • 消息的传递是完全异步的; • 消息是不不可变的; Java Akka 项⽬目包 35
36. Actor编程模型 36
37. Actor编程模型 37
38. Actor编程模型 < 2.6.x >= 2.6.x 线程模型(Netty) 事件模型(Akka) 38
39. Actor编程模型 • Go实现的CSP模式和Actor模式都是通过消息传递的⽅方式来避免共享,主要有以下三个区别 • Actor模型中没有channel,Actor模型中的Mailbox与channel⾮非常类似,看起来都是FIFO队列列,但本质区 别很⼤大 • Actor模型 • Mailbox对程序员是透明的,Mailbox明确归属于某⼀一个特定的Actor,是Actor模型的内部 机制; • • • • Actor之间可以直接通信,不不需要通信媒介; CSP模型 • channel对于程序员来说是可⻅见的; • channel是通信媒介,传递的消息都直接发送到channel中; Actor模型中发送消息是⾮非阻塞的,⽽而CSP模型中是阻塞的 • Go实现的CSP模型,channel是⼀一个阻塞队列列; • 当阻塞队列列已满的时候,向channel发送数据,会导致发送消息的协程阻塞; Actor模型理理论上不不保证消息百分⽐比送达,⽽而Go实现的CSP模型中,是能保证消息百分百送达的(代 价:可能导致死锁) 39
40. CSP编程模型 • Go是⼀一⻔门从语⾔言层⾯面⽀支持并发的编程语⾔言,⽀支持并发也是Go⾮非常重要的特性之⼀一; • Go⽀支持协程,协程可以类⽐比Java中的线程,解决并发问题的难点在于线程(协程)之间的协作; • Go提供了了两种⽅方案 • ⽀支持协程之间以共享内存的⽅方式通信,Go提供了了管程和原⼦子类来对协程进⾏行行同步控制,该⽅方案与Java类似; • ⽀支持协程之间以消息传递的⽅方式通信,本质上是要避免共享,该⽅方案是基于CSP模型实现的,Go推荐该⽅方 案; • ⽣生产者-消费者模式 • 可以把Go实现的CSP模式类⽐比成⽣生产者-消费者模式,⽽而channel类⽐比成⽣生产者-消费者模式中的阻塞队列列; • Go中channel的容量量可以为0,容量量为0的channel被称为⽆无缓冲的channel,容量量⼤大于0的channel被称为有 缓冲的channel; • ⽆无缓冲的channel类似于Java中提供的SynchronousQueue,主要⽤用途是在两个协程之间做数据交换; • Go中的channel是语⾔言层⾯面⽀支持的,使⽤用左向箭头<-完成向channel发送数据和读取数据的任务; • Go中的channel是⽀支持双向传输的,即⼀一个协程既可以通过它发送数据,也可以通过它接收数据; • Go中的双向channel可以变成⼀一个单向channel • calc中创建了了⼀一个双向channel,但是返回的是⼀一个只能接收数据的单向channel; • 所以在主协程中,只能通过该channel接收数据,⽽而不不能通过它发送数据; 40
41. CSP编程模型 41
42. CSP编程模型 42
43. CSP编程模型 • CSP 全称为 Communicating Sequential Process,是⼀一种并发编程模型,由 Tony Hoare 于 1977 年年提出( Communicating Sequential Processes )。简单来说,CSP 模型由并发执⾏行行的实体(线程或者进程)所组成,实体之间通过发送消息进⾏行行通信。 这⾥里里发送消息时使⽤用的就是通道,或者叫 channel。Go 实现了了CSP 部分理理论,其中 goroutine 对应 CSP 的并发执⾏行行实体,channel 对应通道; • 哲学思想:Do not communicate by sharing memory; instead, share memory by communicating(不不要以共享内存的⽅方式来通信,相反,要通过通信来共享内存); 43
44. 哲理理 不不要以共享内存⽅方式通信,要以通信⽅方式共享内存; Actor 模型的重点在于参与交流的实体; CSP 模型的重点在于⽤用于交流的通道; 44
45. CSP编程模型 VS Actor模型 Actor 模型中没有Channel; Actor 模型中发送消息是⾮非阻塞的,⽽而CSP模型是阻塞的; 消息送达,Actor理理论上不不是百分之百送达,CSP是能保证百分之百送达,有死锁⻛风险; 45
46. 并发设计模式【Immutability模式】 • 定义:对象⼀一旦被创建之后,状态就不不再发⽣生变化; • 怎么实现? • 类和属性都是 final 的,所有⽅方法均是只读的; • 将⼀一个类所有的属性都设置成final的,并且只允许存在只读的⽅方法,那么这个类 基本上就具备不不可变性了了。更更严格的做法是这个类本身就是final的,也就是不不允 许继承; • 问题来了了,不不可变性的类,如果需要提供修改的功能呢?那可以创建⼀一个新的不不 可变对象,这与可变对象的重要区别是,可变对象往往可以修改⾃自⼰己的属性; • 问题⼜又来了了,所有的修改操作都创建⼀一个新的不不可变对象,你可能会担⼼心,是不不 是创建太多了了,有点浪费内存呢?是的,那如何解决呢?享元模式 46
47. 并发设计模式【Immutability模式】 • 47
48. 并发设计模式【Immutability模式】 • 享元模式 • 可以减少创建对象的数量量,从⽽而减少内存占⽤用; • 其实就是⼀一个对象池; • 使⽤用场景 • String • Integer • Long • Apache Commons Pool 48
49. 并发设计模式【Copy-On-Write模式】 • 类Unix操作系统调⽤用fork(),会创建⽗父进程的⼀一个完整副本,很耗时; • Linux调⽤用fork(),创建⼦子进程时并不不会复制整个进程的地址空间,⽽而是让⽗父⼦子进程共享 同⼀一个地址空间; • 只有在⽗父进程或者⼦子进程需要写⼊入时才会复制地址空间,从⽽而使⽗父⼦子进程拥有各 ⾃自独⽴立的地址空间; • 本质上来说,⽗父⼦子进程的地址空间和数据都是要隔离的,使⽤用Copy-on-Write更更多体现 的是⼀一种延时策略略 • Copy-on-Write还⽀支持按需复制,因此在操作系统领域能够提升性能 • Java提供的Copy-on-Write容器器,会复制整个容器器,所以在提升读操作性能的同时,是 以内存复制为代价的 • CopyOnWriteArrayList / CopyOnWriteArraySet 49
50. 并发设计模式【ThreadLocal模式】 • 多个线程同时读写同⼀一个共享变量量会存在并发问题; • Immutability模式和Copy-on-Write模式,突破的是写; • ThreadLocal模式,突破的是共享变量量; • ThreadLocalMap,确保线程安全; • 内存泄漏漏 • 线程池中线程的存活时间,往往都是和程序同⽣生共死的,这意味着Thread 持有的 ThreadLocalMap ⼀一直都不不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引⽤用(WeakReference),所以只要ThreadLocal结束了了⾃自⼰己 的声明周期是可以被回收掉的。但是Entry 中的 Value 却被Entry强引⽤用的,所以 Value的声明周期结束了了,但是 Value也是⽆无法被回收的,导致内存泄漏漏; • try{}finally{} ⼿手动释放资源利利器器; 50
51. 并发设计模式【Guarded Suspension模式】 • Guarded Suspension模式是等待唤醒机制的规范实现; • Guarded Suspension模式也被称为Guarded Wait 模式、Spin Lock 模式; • 可以⽤用“多线程版本的if”来理理解; • Guarded Suspension直译为保护性暂停; • 等待条件满⾜足 • ⽐比如,项⽬目组团建要外出聚餐,我们提前预订了了⼀一个包间,然后兴冲冲地奔过 去,到那⼉儿后⼤大堂经理理看了了⼀一眼包间,发现服务员正在收拾拾,就会告诉我们:“您 预订的包间服务员正在收拾拾,请您稍等⽚片刻。”过了了⼀一会,⼤大堂经理理发现包间已经 收拾拾完了了,于是⻢马上带我们去包间就餐; 51
52. 并发设计模式【Balking模式】 • Balking模式本质上是⼀一种规范化地解决“多线程版本的if”的⽅方案; • volatile + Balking模式 • ⽤用synchronized实现Balking模式的⽅方式最为稳妥,建议在实际⼯工作中采⽤用; • 如果对原⼦子性没有要求,可以使⽤用volatile(仅能保证可⻅见性)来实现Balking模 式; • Balking模式只需要互斥锁就能实现,⽽而Guarded Suspension模式则需要⽤用到管程 (⾼高级并发原语); • 从应⽤用⻆角度来看,两者都是为了了解决“线程安全的if”; • Guarded Suspension模式会等待if条件为真(利利⽤用管程模型来实现),⽽而 Balking模式不不会等待; 52
53. 并发设计模式【Balking模式】 53
54. 并发设计模式【Thread-Per-Message模式】 • 就是为每⼀一个任务分配⼀一个独⽴立的线程; • Java中的线程是⼀一个重量量级的对象,创建成本很⾼高(创建过程⽐比较耗时 + 线程占⽤用的内存也较⼤大); • 所以在Java中为每个请求创建⼀一个新的线程并不不适合⾼高并发场景; • 语⾔言、⼯工具和框架本身是帮助我们更更敏敏捷地实现⽅方案的; • • • Java线程和操作系统线程是⼀一⼀一对应的,Java将Java线程的调度权完全委托给操作系统; • 操作系统在线程调度⽅方⾯面⾮非常成熟、稳定和可靠,但创建线程的成本很⾼高; • 为此,JUC提供了了线程池等⼯工具类; 业界还有另外⼀一种解决⽅方案,叫作轻量量级线程; • 在Go语⾔言,Lua语⾔言⾥里里的协程,本质上是⼀一种轻量量级线程; • 轻量量级线程的创建成本很低,基本上和创建⼀一个普通对象的成本类似; • • ⽽而Thread-Per-Message模式是最简单的分⼯工⽅方案,只是Java⽆无法有效⽀支持; • 创建速度和内存占⽤用相⽐比操作系统线程⾄至少有⼀一个数量量级的提升; • 因此,基于轻量量级线程实现Thread-Per-Message模式是没有问题的; OpenJdk的Loom项⽬目,是为了了解决Java语⾔言的轻量量级线程问题,Loom项⽬目中的轻量量级线程叫作Fiber; Thread-Per-Message模式在Java领域并不不知名 • 根本原因:Java线程是⼀一个重量量级对象,线程的创建成本太⾼高,在⾼高并发领域,基本不不具备可⾏行行性 • Java在未来⼀一定会提供轻量量级线程 54
55. 并发设计模式【Worker Thread模式】 • Worker Thread模式可以类⽐比现实世界⾥里里⻋车间的⼯工作模式,Worker Thread对应⻋车间⾥里里 的⼯工⼈人(⼈人数确定); • ⽤用阻塞队列列做任务池,然后创建固定数量量的线程消费阻塞队列列中的任务 – 这就是Java 中的线程池⽅方案; • Thread-Per-Message模式:主线程直接创建⼦子线程,主⼦子线程之间可以直接通信; • Worker Thread模式:主线程提交任务到线程池,但主线程并不不关⼼心任务被哪个线程执 ⾏行行; • 能够避免线程频繁创建、销毁的问题,并且能够限制线程的最⼤大数量量; • Java利利⽤用Worker Thread模式来实现线程池; 55
56. 并发设计模式【2PC模式】 • 第⼀一阶段线程T1向线程T2发送终⽌止指令,第⼆二阶段是线程T2响应终⽌止指令; • Java线程池:提交到线程池的任务,⾸首先进⼊入⼀一个阻塞队列列,然后线程池中的线程从阻塞队列列 中取出任务执⾏行行; • shutdown()是⼀一种很保守的关闭线程池的⽅方法,拒绝接收新的任务,但会等待线程池中正在执 ⾏行行的任务和已经进⼊入阻塞队列列的任务都执⾏行行完后才关闭线程池; • shutdownNow()相对激进,拒绝接收新的任务,会中断线程池中正在执⾏行行的任务(因此需要优 雅地结束,并正确地处理理线程中断)已经进⼊入阻塞队列列中的任务会被剥夺执⾏行行的机会,并且作 为shutdownNow()的返回值返回; 56
57. 并发设计模式【Producer-Consumer模式】 • ⽣生产者-消费者模式的核⼼心是⼀一个任务队列列; • ⽣生产者线程⽣生产任务,并将任务添加到任务队列列中,消费者线程从任务队列列中获 取任务并执⾏行行; • 从架构设计的⻆角度来看,⽣生产者-消费者模式有⼀一个很重要的优点:解耦 • ⽣生产者-消费者模式另⼀一个重要的优点是⽀支持异步,并且能够平衡⽣生产者和消费者的速 度差异(任务队列列) 57
58. 并发设计模式【Producer-Consumer模式】 58
60. THANK YOU 欢迎⼤大家⼀一起交流、学习;huiyang.hou@qq.com;15810921165