锁 - 信号量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   疯狂的妞妞  阅读(167)  评论(0编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示