浅谈基本的并发模式——Signaling

浅谈基本的并发模式——Signaling

  最近在阅读《Little Book Of Semaphores》,收获良多,总结一下书中第三章———基本的并发模式(Basic synchronization patterns)。文中主要介绍了Signaling(通知)、Rendezvous(约会)、Mutex(互斥)、Multiplex(...)、Barrier(屏障)、Queue(队列)六种模式。

Signaling(通知)

  Signaling是一个线程发送信号给另一个线程表明一件事情发生,涉及到发送线程和接受线程。发送线程完成事件A时会发送信号。接受线程运行到一定位置时等待或检查事件A的完成信号,只有收到事件A的完成信号才可以继续运行。Signaling可以用于保证不同线程中代码块的执行顺序,解决Serialization Problem(一致性问题?)。



可以简单的使用AtomicBoolean来变量来实现

// AtomicBoolean版的Signaling
public class Signaling {
    private AtomicBoolean state = new AtomicBoolean(false);

    public void doSignal() {
        state.set(true);
    }

    public void doWait() {
        for (;;) {
            if (state.get() && state.compareAndSet(true, false)) {
                break;
            }
        }
    }
}

使用AtomicBoolean实现的Signaling在doWait调用前的多次doSignal的调用时没有作用的,在使用上有很多限制。更好的实现可以使用AtomicInteger。

// AtomicInteger版的Signaling
public class Signaling {
    private AtomicInteger state;

    public Signaling() {
        this(0);
    }

    public Signaling(int permits) {
        state = new AtomicInteger(permission);
    }

    public void doSignal() {
        state.incrementAndGet();
    }

    public void doWait() {
        state.decrementAndGet();
        while (state.get() < 0) {
        }
    }
}

测试程序:

    public static void main(String[] args) {
        Signaling signaling = new Signaling();
        Thread a = new Thread(() -> {
            System.out.println("A1");
            signaling.doSignal();
            System.out.println("A2");
        });

        Thread b = new Thread(() -> {
            System.out.println("B1");
            signaling.doWait();
            System.out.println("B2");
        });

        b.start();
        a.start();
    }

A1和B1、A2和B2的打印顺序无法确定,由于Signaling的使用A1一定在B2之前打印。使用这种方法会使接受线程在一个变量上自旋,如果等待的时间不长还可以接受否则会造成CPU资源的浪费。



  《Little Book Of Semaphores》中主要讲Semaphores(信号量)以及使用Semaphores解决并发中一些问题。我们也可以使用Java并发包中的Semaphores表明Signaling。

// Semaphores版的Signaling
public class Signaling {
    private Semaphore semaphore;

    public Signaling() {
        this(0);
    }

    public Signaling(int permits) {
        semaphore = new Semaphore(permits);
    }

    public void doSignal() {
        semaphore.release();
    }

    public void doWait() {
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  关于Signaling的使用举一个都很熟悉的问题————两个线程交替打印奇偶数(这里就不给出问题描述了)。这个问题很明显的要解决两个线程中打印代码的执行顺序,我们可以使用Signaling的思想来思考这个问题。每个线程都在循环中使用同样的方式遍历所有的数字,在偶数线程中当遇到偶数时则打印并发出通知,遇到奇数则等待通知。奇数线程则相反。

    public static void main(String[] args) {
        Signaling even = new Signaling();
        Signaling odd = new Signaling();
        final int TOTAL = 100;
        Thread evenThread = new Thread(() -> {
            for (int i = 0; i <= TOTAL; i++) {
                if (i % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    even.doSignal();
                } else {
                    odd.doWait();
                }
            }
        });
        Thread oddThread = new Thread(() -> {
            for (int i = 0; i <= TOTAL; i++) {
                if (i % 2 == 1) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    odd.doSignal();
                } else {
                    even.doWait();
                }
            }
        });
        evenThread.start();
        oddThread.start();
    }

  两个线程交替打印奇偶数可以扩展到N个线程连续打印数字0到M(怎么才能清晰的描述这个问题?)。我们使用N个Signaling来控制N个线程的执行顺序,一个线程打印完通知应该打印下一个数字的线程,线程在等待打印上一个数字的线程上,线程只关心它应该打印的数字的上一个数字有没有打印。

    public static void main(String[] args) {
        final int NUM = 6;
        final int TOTAL = 200;
        Signaling[] signalings = new Signaling[NUM];
        for (int i = 0; i < NUM; i++) {
            signalings[i] = new Signaling();
        }

        Thread[] threads = new Thread[NUM];
        for (int i = 0; i < NUM; i++) {
            int index = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j <= TOTAL; j++) {
                    int current = j % NUM;
                    int prev;
                    if (index - 1 < 0) {
                        prev = NUM - 1;
                    } else {
                        prev = index - 1;
                    }
                    if (current == index) {
                        System.out.println(Thread.currentThread().getName() + ": " + j);
                        signalings[(index + 1) % NUM].doSignal();
                    } else if (current == prev) {
                        signalings[index].doWait();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " end");
            });
        }

        for (int i = 0; i < num; i++) {
            threads[i].start();
        }
    }

猜你喜欢

转载自www.cnblogs.com/xtaieer/p/10704032.html
今日推荐