PV 操作是一种实现进程 互斥同步 的有效方法。PV 操作与信号量的处理相关,P 表示 passeren 通过的意思,V 表示 vrijgeven 释放的意思. 包含在 1965 年, 由荷兰人 Dijkstra 提出的信号量机制; (同是 银行家算法 和 最短路径算法 的提出者)

术语:

  1. semaphore
  2. mutually-exclusive
  3. synchronization

介绍

  1. 信号量 S
  • S >= 0 表示某资源的的可用数;
  • S < 0 表示其绝对值表示阻塞队列中等待改资源的进程数;

P 操作表示 申请一个资源
V 操作表示 释放一个资源

  1. P 操作: S := S - 1
  • S >= 0, 则执行 P 操作的进程继续执行
  • S < 0, 则将执行该操作的进程置为阻塞状态, 并将其加入到 “阻塞队列”
  1. V 操作: S := S + 1
  • S > 0, V 操作继续
  • S <= 0, 则从阻塞队列唤醒一个进程, 并将其加入到 “就绪队列”

互斥与同步

  1. 互斥
    在这里插入图片描述
  2. 同步
  • 使用两个信号量来同步 (协作)
  1. buffer_empty: 空闲量

  2. buffer_full: 存放量
    在这里插入图片描述

  3. 同步与互斥的区别

同步是指两个进程协调来完成一件事情 (whole), 互斥则表示同一时刻只能有一个进程进入临界区 (fragment)

代码

  • ABA
class SemaphoreABA implements FooBar {

    private final int n;
    Semaphore foo = new Semaphore(6);
    Semaphore bar = new Semaphore(0);

    public SemaphoreABA(int n) {

        this.n = n;
    }

    public static void main(String[] args) {

        SemaphoreABA fooBar = new SemaphoreABA(10);
        new Thread(() -> {
            try {
                fooBar.foo(() -> System.out.print("Foo"));
            } catch (InterruptedException ignored) {
            }
        }).start();
        new Thread(() -> {
            try {
                fooBar.bar(() -> System.out.print("Bar"));
            } catch (InterruptedException ignored) {
            }
        }).start();
    }

    public void foo(Runnable printFoo) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            foo.acquire();
            printFoo.run();
            bar.release();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            bar.acquire();
            printBar.run();
            foo.release();
        }
    }

}
  • ABC
class SemaphoreThreadABC implements Foo {

    private final Semaphore s2;
    private final Semaphore s3;

    public SemaphoreThreadABC() {

        s2 = new Semaphore(0);
        s3 = new Semaphore(0);
    }

    public static void main(String[] args) {

        SemaphoreThreadABC foo = new SemaphoreThreadABC();
        new Thread(() -> {
            try {
                foo.second(() -> System.out.println("Two"));
            } catch (InterruptedException ignored) {
            }
        }).start();
        new Thread(() -> {
            try {
                foo.first(() -> System.out.println("One"));
            } catch (InterruptedException ignored) {
            }
        }).start();
        new Thread(() -> {
            try {
                foo.third(() -> System.out.println("Three"));
            } catch (InterruptedException ignored) {
            }
        }).start();
    }

    public void first(Runnable printFirst) throws InterruptedException {

        printFirst.run();
        s2.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {

        s2.acquire();
        printSecond.run();
        s3.release();
    }

    public void third(Runnable printThird) throws InterruptedException {

        s3.acquire();
        printThird.run();
    }

}

扩展

  1. 虚假唤醒 spurious wakeup (aka. 过早唤醒)

用 while 替换 if

if (state % 3 == 0) {
    // spurious wakeup
    System.out.print("A");
    state++;
    obj.await();
}
... // 虚假唤醒后, 继续往下走了

// 改正后
while (state % 3 == 0) {
    // spurious wakeup
    System.out.print("A");
    state++;
    obj.await();
}

在使用 notifyAll() 时, 所有线程全被唤醒, 但并不是所有线程都满足条件, 此时如果不用重新判断, 就会继续往下走

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐