Concurrent包

2020-04-06  本文已影响0人  凯玲之恋

1.并发机制

1.1 并发的原理

1.2 常见的并发机制

常用并发机制.png

1.3 不同系统的并发机制

2.互斥

2.1 互斥的要求

2.2 互斥方案

2.3 互斥的软件方法支持

2.3.1 Dekker算法

/**
 * 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 读者有兴趣可自己验证一下**/
    }
}

2.3.2 Peterson算法

/**
 * 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 读者有兴趣可自己验证一下**/
    }
}

2.4 互斥的硬件支持

2.4.1 中断禁用

2.4.1.1 中断禁用综述

//由于临界区不能被中断,因此可以保证互斥
while(true){
    /** disable interrupt 禁用中断 **/
    /** critical zone 临界区 **/
    /** enable interrupt 启用中断 **/
    /** do other 其他部分 **/
}

2.4.1.2 中断禁用问题

2.4.2 专用机器指令

2.4.2.1 专用机器指令综述

2.4.2.2 专用机器指令问题

2.5 互斥的系统或语言级别支持

2.5.1 信号量

2.5.1.1 信号量概述

2.5.1.2 信号量的实现(CAS版)

/**
 * 设计原则:任何时候只能一个线程可以用wait和signal操作控制一个信号量
 * 要求:semWait和semSingal操作必须作为原子原语实现
 * semaphore信号量(以下都简称sem)的属性 
 *      flag : 表示信号量是否可用,默认是0
 *      count: 
 *          当>=0时,表示可执行semWait而不被挂起的线程数
 *          当<0时,表示挂起在信号量的等待队列的线程数
 *      queue: 表示信号量相关联的等待队列,被阻塞的线程需放入该队列中
 *  PS:这里我们选用Boolean版本的CAS
 */
semWait(sem){
    //当发现sem.flag不为0时,就自旋等待直到为0
    //补充一点:忙等待可以保证队列操作的同步,
    //但由于wait和signal执行时间短,其开销还是很小的
    while(!compare_and_swap(sem.flag,0,1));
    sem.count--;
    if(sem.count < 0){
        /** 该线程进入sem.queue等待队列中并被阻塞 **/
    }
    sem.flag = 0;
}
semSignal(sem){
    //当发现sem.flag不为0时,就自旋等待直到为0
    while(!compare_and_swap(sem.flag,0,1));
    sem.count++;
    if(sem.count <= 0){
        /** 从sem.queue等待队列中移出,被移出的线程进入就绪队列**/
    }
    sem.flag = 0;
}

2.5.1.3 信号量实现互斥

final int n = /** 线程数 **/
int s = 1;//semaphore
public void P(int i){
    while(true){
        semWait(s);
        /** critical zone 临界区 **/
        semSignal(s);
        /** do other 其他部分 **/
    }
}

2.5.2 管程

2.5.2.1 管程概述

2.5.2.2 条件变量

2.5.2.3 管程使用

2.5.3 消息传递

2.5.3.1 消息传递概述

2.5.3.2 消息结构

消息.jpg

2.5.3.3 消息通信情况

消息通信情况.jpg

2.5.3.3 消息通信组合

2.5.3.4 消息通信寻址

2.5.3.5 消息通信实现互斥

int n = /* 线程数 */
void P(int i){
    Message msg = /* 消息 */
    while(true){
        receive(box,msg);
        /* 临界区 */
        send(box,msg)
    }
}
send(box,null);//信箱被初始化为一个无内容的消息

2.5.4 读/写优先

3.Concurrent并发包

3.1 Concurrent包整体架构

Concurrent包整体架构.jpg

3.2 Concurrent包整体类图

并发包.jpg

3.3 Concurrent包实现机制

3.3.1 底层-硬件指令支持

3.3.2 中间层-基础数据结构+算法支持

3.3.3 高层-并发类库支持

参考

并发番@Concurrent包一文通(1.8版)

上一篇下一篇

猜你喜欢

热点阅读