并发工具:Semaphore工具(一)

1 Semaphore介绍

Semaphore(信号量)是一个线程同步工具,主要用于在一个时刻允许多个线程对共享资源进行并行操作的场景。通常情况下,使用Semaphore的过程实际上是多个线程获取访问共享资源许可证的过程Semaphore(信号量)是一个线程同步工具,主要用于在一个时刻允许多个线程对共享资源进行并行操作的场景。通常情况下,使用Semaphore的过程实际上是多个线程获取访问共享资源许可证的过程。

Semaphore的内部处理逻辑如下:

  • 如果此时Semaphore内部的计数器大于零,那么线程将可以获得小于该计数器数量的许可证,同时还会导致Semaphore内部的计数器减少所发放的许可证数量。
  • 如果此时Semaphore内部的计数器等于0,也就是说没有可用的许可证,那么当前线程有可能会被阻塞(使用tryAcquire方法时不会阻塞)。
  • 当线程不再使用许可证时,需要立即将其释放以供其他线程使用,所以建议将许可证的获取以及释放动作写在try…finally语句块中。
    在这里插入图片描述

2 入门案例:限制用户登录数量

模拟某个登录系统,最多限制给定数量的人员同时在线,如果所能申请的许可证不足,那么将告诉用户无法登录,稍后重试。

import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author wyaoyao
 * @date 2021/4/22 10:10
 */
public class SemaphoreExample1 {

    public static void main(String[] args) {
        final int MAX_PERMIT_LOGIN_ACCOUNT = 10;
        final LoginService loginService = new LoginService(MAX_PERMIT_LOGIN_ACCOUNT);
        // 开启20个线程登录
        IntStream.range(0, 20).forEach(i -> {
            new Thread(() -> {
                boolean login = loginService.login();
                if(!login){
                    System.out.println(Thread.currentThread().getName() + " 拒绝登录请重试");
                    return;
                }
                // 模拟登录系统后的操作
                try {
                    TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 退出
                loginService.loginOut();
            }, "T" + i).start();
        });

    }

    static class LoginService {
        final int max_permit_login_account;
        // 许可证
        private final Semaphore semaphore;

        LoginService(int max) {
            this.max_permit_login_account = max;
            this.semaphore = new Semaphore(max);
        }

        public boolean login() {
            // 获取许可证,如果获取失败该方法会返回false,tryAcquire不是一个阻塞方法
            boolean b = semaphore.tryAcquire();
            if (b) {
                System.out.println(Thread.currentThread().getName() + "登录成功");
            }
            return b;
        }

        public void loginOut() {
            // 这里就简单处理:释放许可证
            semaphore.release();
            System.out.println(Thread.currentThread().getName() + "退出登录");
        }
    }
}

Semaphore的许可证数量为10,这就意味着当前的系统最多只能有10个用户同时在线,如果其他线程在Semaphore许可证数量为0的时候尝试申请,就将会出现申请不成功的情况,输出结果:

T0登录成功
T4登录成功
T3登录成功
T2登录成功
T1登录成功
T6登录成功
T7登录成功
T5登录成功
T8登录成功
T9登录成功
T10 拒绝登录请重试
T11 拒绝登录请重试
T12 拒绝登录请重试
T13 拒绝登录请重试
T14 拒绝登录请重试
T15 拒绝登录请重试
T17 拒绝登录请重试
T18 拒绝登录请重试
T19 拒绝登录请重试
T16 拒绝登录请重试
T5退出登录
T6退出登录
T0退出登录
T9退出登录
T4退出登录
T7退出登录
T8退出登录
T3退出登录
T1退出登录
T2退出登录

如果将tryAcquire方法修改为阻塞方法acquire,那么我们会看到所有的未登录成功的用户在其他用户退出系统后会陆陆续续登录成功:

public boolean login() {
    // 获取许可证,如果获取失败该方法会返回false,tryAcquire不是一个阻塞方法
    try {
        semaphore.acquire();
        System.out.println(Thread.currentThread().getName() + "登录成功");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return true;
}

3 案例:使用Semaphore定义try lock

synchronized关键字:当某个时刻获取不到锁的时候,当前线程会进入阻塞状态。这种状态有些时候并不是我们所期望的,如果获取不到锁线程还可以进行其他的操作,而不一定非得将其阻塞(事实上,Lock接口中就提供了try lock的方法,当某个线程获取不到对共享资源执行的权限时将会立即返回,而不是使当前线程进入阻塞状态),本节将借助Semaphore提供的方法实现一个显式锁,该锁的主要作用是try锁,若获取不到锁就会立即返回。

public class SemaphoreExample2 {
    private static class TryLock {
        // 定义许可证数量为1,因为只会有一个线程获取到锁
        private final Semaphore semaphore = new Semaphore(1);

        /**
         * 尝试加锁
         *
         * @return: 是否获取到锁
         */
        public boolean tryLock() {
            return semaphore.tryAcquire();
        }

        /**
         * 释放锁
         */
        public void unLock() {
            semaphore.release();
            System.out.println(Thread.currentThread().getName() + "release lock");
        }
    }

    public static void main(String[] args) {
        final TryLock tryLock = new TryLock();
        // 启动一个线程,尝试获取tryLock,如果获取不成功则将进行其他的操作,该线程不用进入阻塞状态
        new Thread(() -> {
            boolean getLock = tryLock.tryLock();
            if (!getLock) {
                // 没有获取到锁
                System.out.println(Thread.currentThread().getName() + " can't get the lock, will do other thing.");
                return;
            }
            // 获取到锁
            try {
                System.out.println(Thread.currentThread().getName() + " get the lock and do working...");
                // 模拟执行耗时
                TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                tryLock.unLock();
            }
        }, "T1").start();
        // main线程也会参与trylock的争抢,同样,如果抢不到trylock,则main线程不会进入阻塞状态
        boolean gotLock = tryLock.tryLock();
        if (!gotLock) {
            System.out.println(Thread.currentThread().getName() + " can't get the lock, will do other thing.");

        } else {
            // 模拟执行耗时
            try {
                // 模拟执行耗时
                System.out.println(Thread.currentThread().getName() + " get the lock and do working...");
                TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                tryLock.unLock();
            }
        }
    }
}

借助于只有一个许可证的Semaphore进行tryAcquire的操作,运行代码我们可以看到如下的结果,没有抢到锁的线程也会立即返回,并不会导致当前线程进入阻塞状态中:

main get the lock and do working...
T1 can't get the lock, will do other thing.
main release lock
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值