并发编程无论在哪个服务器端语言中都是位居高级进阶位置。
1.并发编程面临的问题?
如何解决好 ,分工 、同步(协作)、互斥?
1.1分工
分工就涉及到业务的拆分,这个和我们做项目一样,先进性 需求分析,划分模块,拆任务、评估工期,然后 将任务分配 到个人手里面(线程手里)。
分工完毕后,我们常用多线程、线程池、fork/join方式处理可以并行进行的任务。
1.2 同步
在项目的开发中,具体的任务之间可能有依赖呢,例如你需要调用你同事的某个接口提供服务,等到你用到的时候,可以先等着他把具体的接口定义定义完毕后,然后再进行后续的开发工作。
两个开发人员(线程)可能产生依赖关系,开发过程中,可能发现 A需要 B提供的某个接口才能继续编程,这样就需要 B先进行接口定义,定义完毕后,通知A,A继续进行当前业务开发处理。
各种工具类: CountDownLatch 、 Exchanger 、CycileBarrier 等等。
1.3 互斥
公司为了节省成本只有一个UI设计人员(服务于多个项目),有两个项目同时需要 UI人员进行图形设计,同一时刻这个 UI开发人员,只能完成 其中一个 项目的图片任务。这就有了竞争,一般情况下
会根据项目优先级进行排期,优先级低的项目就只能(阻塞)等待着优先级高的处理完毕了,再进行。
互斥一般的实现手段都是加锁
从操作系统原理层面上来讲,我了解了两种 1、信号量 ; 2 、管程
2.信号量 和管程
2.1 信号量
为什么要先聊信号量呢?因为这个先出现的。 信号量机制是由大名鼎鼎的荷兰计算机科学家 Dijkstra 于1965 年提出。
信号量 模型 粗略描述
两个成员变量:
1. 整型数 count:用于记录共享资源数量 可以参考 Java 的 Semaphore,这个 初始值 count=2 表示 ,可以同时容忍两个线程进入操作某个资源。
2. 等待队列
- 两个基本方法:
- P() 操作:将 count - 1,若 count < 0 时,线程进入等待队列(Prolaag 荷兰语,尝试减少) 例如:初始化count=2, 调用两次 P()后,count=0; 再次调用的时候就得放入等待队列了,然后阻塞了。
- V() 操作:将 count + 1,若 count <= 0 时,唤醒一个等待队列中的线程(Verhoog 荷兰语,增加)
对信号量模型需要注意以下几点:
- 初始化信号量时,我们指定 count 的值,之后 count 值的变化只能由 P() / V() 进行操作
- 由操作系统实现并保证 P() / V() 操作的原子性
- P() 操作是可能阻塞的,V() 操作不会阻塞
- P() / V() 操作必须成对出现
信号量机制相比于锁机制的一个主要区别是:信号量机制允许多个线程同时访问一个临界区
因为只要 count 符合条件,那么线程就可以访问临界区资源
信号量机制也存在一些缺点,那就是真正使用起来比较困难,因为当我们需要解决的同步问题越复杂,条件越多,那么需要的信号量就越多,需要更加谨慎地处理信号量之间的处理顺序否则很容易造成死锁现象。
2.2 管程
JDK的synchornized 就是用的管程实现的。
如果扒开 字节码分析 可以看到代码里面多了
管程的 组成如下:
- 共享变量
- 入口等待队列
- 一个锁:控制整个管程代码的互斥访问
- 0个或多个条件变量:每个条件变量都包含一个自己的等待队列,以及相应的出/入队操作。 最初 我们 用的最多的就是 wait 和notifyAll 了。
注意:在入口等待队列中的线程,都是条件满足情况未知或曾经满足过条件的线程,它们等待管程的新一轮执行,而条件变量的等待队列中的线程必须等到满足该条件后才可以重新回到入口等待队列中去
管程模型相较于信号量模型的好处就是:易用,它可以把多个同步条件的操作都集中在一个模块里,从而简化同步机制的实现难度
参考
《极客时间》并发编程
https://zhuanlan.zhihu.com/p/150573153