什么是Semaphore(信号量)

一.什么是Semaphore

 Semaphore中管理一组虚拟的许可(permit),许可的初始数量可以通过构造函数指定。在操作时可以首先获得许可(只要还有剩余的许可),并且在使用以后释放许可。如果没有许可,那么acquire阻塞直到有许可(或者直到被中断或者操作超时)。release方法将返回一个许可给信号量。

                                                                      ------ 摘自《Java并发编程实战》

在《Java并发编程实战》有个例子,在类中 BoundHashSet中,把信号量作为set集合的边界使用。在使用set的add方法前,我们会去像信号量获取许可(permit),有则会添加到set,没有则会一直阻塞(CAS,自旋锁)。注意,许可的数量set限定的长度一致。

这里的场景:当超过指定的set集合长度后,不再能够继续插入,而且持续阻塞,一直等待到有线程在把“许可”归还后,获取“许可”继续执行。

 BoundHashSet类

public class BoundHashSet<T> {
    private final Set<T> set;
    private final Semaphore sem;
    //限定set的容量和信号量许可的数量
    public BoundHashSet(int bound){
        this.set = Collections.synchronizedSet(new HashSet<>(bound));
        this.sem = new Semaphore(bound);
    }
    public boolean add(T o) throws InterruptedException {
        //获取“许可”,没有获取到会阻塞
        sem.acquire();
        boolean wasAdded = false;
        try {
            System.out.println("正在添加 "+o+"并获得许可");
            wasAdded = set.add(o);
            return wasAdded;
        }finally {
            if (!wasAdded){
                //没有添加成功,则释放信号量
                sem.release();
            }
        }
    }
    public boolean remove(Object o){
        boolean wasRemoved = set.remove(o);

        if (wasRemoved){
            System.out.println("正在移除 "+o+"并释放许可");
            //移除成功元素,释放信号量
            sem.release();
        }
        return wasRemoved;
    }

    @Override
    public String toString() {
        return Arrays.toString(set.toArray());
    } 
}

 BoundHashSet测试类

public class ThreadTest18 {
    public static void main(String[] args) throws InterruptedException {
        BoundHashSet<String> set = new BoundHashSet<>(5);

        //模拟添加线程
        Thread t1 = new Thread(() -> {
            try {
                set.add("123");
                set.add("abc");
                set.add("1234");
                set.add("12345");
                set.add("123456");
                //模拟添加第6元素,此时程序会一直阻塞,只要有别的许可放出
                set.add("1234567");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });


        //模拟移除线程
        Thread t2 = new Thread(() -> {
            try {
                //假设这里需要花时间
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //假设有一条别的线程进来访问了这个set,并移除了"abc",释放了一个许可
            set.remove("abc");
        });
        t1.start();
        t2.start();
        //join只为打印最后结果
        t1.join();
        t2.join();
        //打印最后结果  "1234567"此时就可以拿到许可,然后插入成功
        System.out.println(set);
    }
}

1.模拟两条线程t1,t2

2.t1线程想要写入超过边界值的数据(例子中边界值是5,即5个“许可”)

3.t1在添加第六个元素的时候,会一直阻塞

4.t2线程花费了1500毫秒时间,然后开始把其中一个元素移除,这里释放一个“许可”

5.最后t2线程完成,然后t1线程终于拿到了一个“许可”,把"1234567"添加到set中

6.最后打印结果


[12345, 123, 1234, 123456, 1234567] 

发布了51 篇原创文章 · 获赞 29 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/u012190514/article/details/105232257