并发编程 - Semaphore

Semaphore 直译:发出信号、打旗语,在编程中通常被称为:计数信号量,或者信号量。

举个生活案例

  • 比如说旅店有10个单人间,有非常多的人要住;
  • 最多只会只有10个人拿到钥匙;
  • 没拿到钥匙的人非要住,那就只能等了;
  • 而拿走钥匙的人,不住了那就必须退还钥匙,不然别人就没办法住。

信号量的使用类似于案例中的钥匙,它可以给任意的东西配一个使用许可,代码内部维护了一个许可集,当线程希望执行某个操作,就必须先获取许可,如有许可集已经空了,在许可可用前,后面申请许可的线程都会进入等待。

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

应用场景举例:读取压缩文件,有多个线程,都想读取这个文件,但是解压步骤,只能一个线程执行。

简单的代码展示

public class Test {
    private static Semaphore available = new Semaphore(5);

    public static void doSomething(){
        // 这里信号量限制为5,最多只能有5个线程访问,多余的将被阻塞
        available.acquireUninterruptibly();
        
        //TODO : 执行别的事情
        
        //释放信号量
        available.release();
    }
}

使用信号量处理线程同步的操作

业务场景:主线程需要5个并发的初始化操作,必须等5个线程全部初始化完毕,主线程继续执行

这个问题解决方案多种多样,信号量、倒数锁、屏障锁都可以做,直接使用同步块也能解决。这里展示信号量的解决方式,代码相对简单。


/**
 * 主线程需要5个并发的初始化操作,5个线程全部初始化完毕,主线程开始执行
 * @author ChenSS on 2018年2月3日
 */
public class Test {
    private static Semaphore available = new Semaphore(5);

    public static void main(String[] args) throws InterruptedException {
        int count = 5;
        // 一次性申请全部信号量
        available.acquireUninterruptibly(5);
        while (count-- > 0) {
            final int n = count;
            new Thread(() -> {
                try {
                    Thread.sleep(3000 + RandomUtils.nextLong(3000));
                    System.out.println("完成第" + n + "个步骤");
                    available.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        // 再次申请全部信号量,在所有线程归还信号量之前,线程阻塞
        available.acquireUninterruptibly(5);
        // 引用置空
        available = null;
        System.out.println("五个初始化操作成功,进行别的操作!");
        
    }
}

官方Demo

/**
 * 官方Demo(类似于连接池):
 * 开始的时候初始化一些元素
 * 每次取出元素前,申请1个信号量,回收元素的时候,归还1个信号量。
 * 当全部元素被取出,下一次取出元素的操作将被阻塞,直到有信号量被归还
 *
 * @author ChenSS on 2018年2月3日
 */
class Pool {
    private static final int MAX_AVAILABLE = 3;
    private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

    //不是十分有效的存储结构
    protected Object[] items = { "aaaa", "bbbb", "cccc" };
    protected boolean[] used = new boolean[MAX_AVAILABLE];

    public Object getItem() throws InterruptedException {
        available.acquire();
        return getNextAvailableItem();
    }

    public void putItem(Object x) {
        if (markAsUnused(x))
            available.release();
    }

    protected synchronized Object getNextAvailableItem() {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (!used[i]) {
                used[i] = true;
                return items[i];
            }
        }
        return null;
    }

    protected synchronized boolean markAsUnused(Object item) {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (item == items[i]) {
                  if (used[i]) {
                    used[i] = false;
                    return true;
                  } else
                    return false;
               }
        }
        return false;
    }

}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Pool pool = new Pool();
        int count = 5;
        while (count-- > 0) {
            if (count < 2){
                //回收元素
                pool.putItem("aaaa");
            }
            System.out.println(pool.getItem());
        }
    }
}

posted on 2018-02-03 08:29  疯狂的妞妞  阅读(161)  评论(0编辑  收藏  举报

导航