Java 并发之Concurrent 包综述
2021-07-13 22:07
标签:设计原则 执行时间 分享图片 区别 期望 color 执行顺序 非阻塞队列 ola ■ 并发原理 单核系统:线程交替执行,由于交替又快又多,给人一种同时执行的感觉 ■ 常见的并发机制 ■ 不同系统的并发机制 ■ 互斥的需求 ■ 互斥的方案 * Dekker算法 * Peterson算法 ■ 信号量 * 信号量的实现 (CAS版) * 信号量实现互斥 ■ 消息传递 (大家比较熟悉的并发机制,当然有时间的话还会专门介绍一个MQ,比如 Rabbit MQ ) 1. 消息传递的概述 2. 消息结构 3. 消息通信情况 ■ Concurrent 包结构 ■ Concurrent 包整体类图 ■ Concurrent包实现机制 1. 底层-硬件指令支持 2. 中间层-基础数据结构+算法支持 3. 高层-并发类库支持 PS: 感谢 黄志鹏kira 的友情赞助,继续加油写出更棒的文章,后续文章会不断改进迭代~ Java 并发之Concurrent 包综述 标签:设计原则 执行时间 分享图片 区别 期望 color 执行顺序 非阻塞队列 ola 原文地址:https://www.cnblogs.com/romanjoy/p/8442858.html
多核系统:不仅可以交替执行线程,而且可以重叠执行线程
补充: 本章指的并发主要指的是线程间的并发
/**
* Dekker算法基本约束:
* 某一时刻对某一内存地只能进行一次访问
* 1.设置flag做为两个线程进入临界区的密钥,当一个线程失败,其他仍可访问
* - 每个线程只能改变自身的flag,只能检查其他线程的flag而不能改变
* - 当一个线程要进入临界区,需周期性检查另一个线程flag,直到另一线程不在临界区
* - 当线程进入临界区时,应立即设置自身flag为true,表明占领临界区
* - 当线程离开临界区时,应立即设置自身flag为false,表明释放临界区
* 2.设置turn用于安排临界区的访问顺序,访问线程必须重复读取turn值直到被允许进入临界区
* - 当turn值等于线程号,该线程可以进入其临界区
* - 否则,该线程必须被强制等待(忙等待或自旋等待)
*/
public class Dekker {
//观察两个线程的状态
boolean[] flag = {false,false};
//表示临界区访问权限的轮转,初始权利给P1 -- 安排执行顺序避免谦让造成的活锁问题
int turn = 1;
public void P0(){
while (true){
//设置P0的flag为true,同时检查P1的flag
flag[0] = true;
while (flag[1]){
//当临界区不可用时,判断当前临界区权限是否是P1
if (turn == 1){ // 用于处理活锁问题
//当临界区权限是P1时,需要将P0设置为false,使得P1能进入临界区,用于处理死锁问题
flag[0] = false;
//循环校验turn的权限(空自旋),直到P1执行完毕将权限交给P0
while (turn == 1){
/** do Nothing 空自旋 **/
}
flag[0] = true;//此时P1应已执行完毕,应当禁止P1进入临界区
}
}
//当P1的flag为false时,P0可以立即进入临界区
/** critical section 临界区 **/
//当临界区执行完之后,turn设置为1,将临界区访问权限交换给P1
//并将P0的flag设置为false,释放临界区,使得P1可进入临界区
turn = 1;
flag[0] = false;
/** do otherThings **/
}
}
public void P1(){
while (true){
//设置P1的flag为true,同时检查P0的flag
flag[1] = true;
while (flag[0]){
//当临界区不可用时,判断当前临界区权限是否是P0
if (turn == 0){ //用于处理活锁问题
//当临界区权限是P0时,需要将P1设置为false,使得P0能进入临界区,用于处理死锁问题
flag[1] = false;
//循环校验turn的权限(空自旋),直到P0执行完毕将权限交给P1
while (turn == 0){
/** do Nothing 空自旋 **/
}
flag[1] = true;//此时P0应已执行完毕,应当禁止P0进入临界区
}
}
//当P0的flag为false时,P1可以立即进入临界区
/** critical section 临界区 **/
//当临界区执行完之后,turn设置为0,将临界区访问权限交换给P0
//同时将P1的flag设置为false,释放临界区,使得P0可进入临界区
turn = 0;
flag[1] = false;
/** do otherThings **/
}
}
public static void main(){
/** 并发执行P0 P1 读者有兴趣可自己验证一下**/
}
}
/**
* Peterson算法比Dekker算法更加简单出色而且很容易推广到多个线程
* 1.互斥保护验证:P0角度
* - 当PO设置flag[0]=true,则P1不能进入临界区
* - 当P1已进入临界区,而flag[1]=true,P0不能进入临界区
* 2.避免相互阻塞验证:P0角度
* - 当P0在while循环中被阻塞,此时flag[1]=true且turn=1
* - 当flag[1]=false或turn=0,此时P0可以进入临界区
* 3.复杂度:该算法用简单的交替进入临界区的方式降低了并发互斥的复杂度
*/
public class Peterson {
boolean[] flag = {false,false};//表明每个互斥线程的位置
int turn = 0;//解决同时发生的冲突
public void P0(){
while (true){
flag[0] = true;
//每次都要显式设置turn=1并作为while空自旋条件,迫使其他线程也有进入临界区的机会
//这也是解决互斥的一个简洁方案,大家依次来,不能重复独占
turn = 1;
while (flag[1] && turn == 1){
/** do Nothing 空自旋**/
}
/** critical section 临界区 **/
flag [0] = false;
/** do otherThings **/
}
}
public void P1(){
while (true){
flag[1] = true;
turn = 0;
while (flag[0] && turn == 0){
/** do Nothing 空自旋**/
}
/** critical section 临界区 **/
flag [1] = false;
/** do otherThings **/
}
}
public static void main(){
/** 并发执行P0 P1 读者有兴趣可自己验证一下**/
}
}
/**
* 设计原则:任何时候只能一个线程可以用wait和signal操作控制一个信号量
* 要求:semWait和semSingal操作必须作为原子原语实现
* semaphore信号量(以下都简称sem)的属性
* flag : 表示信号量是否可用,默认是0
* count:
* 当>=0时,表示可执行semWait而不被挂起的线程数
* 当*/
semWait(sem){
//当发现sem.flag不为0时,就自旋等待直到为0
//补充一点:忙等待可以保证队列操作的同步,
//但由于wait和signal执行时间短,其开销还是很小的
while(!compare_and_swap(sem.flag,0,1));
sem.count--;
if(sem.count ){
/** 该线程进入sem.queue等待队列中并被阻塞 **/
}
sem.flag = 0;
}
semSignal(sem){
//当发现sem.flag不为0时,就自旋等待直到为0
while(!compare_and_swap(sem.flag,0,1));
sem.count++;
if(sem.count ){
/** 从sem.queue等待队列中移出,被移出的线程进入就绪队列**/
}
sem.flag = 0;
}
final int n = /** 线程数 **/
int s = 1;//semaphore
public void P(int i){
while(true){
semWait(s);
/** critical zone 临界区 **/
semSignal(s);
/** do other 其他部分 **/
}
}