Java中Semaphore(信号量)的使用

Semaphore的作用:
在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机、厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制–资源的多副本的并发访问控制,今天学习的信号量Semaphore即是其中的一种。

Semaphore实现原理初探:
Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

主要方法:

方法描述
void acquire()从信号量获取一个许可,如果无可用许可前将一直阻塞等待
void acquire(int permits)获取指定数目的许可,如果无可用许可前也将会一直阻塞等待
boolean tryAcquire()从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞
boolean tryAcquire(int permits)尝试获取指定数目的许可,如果无可用许可直接返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit)在指定的时间内尝试从信号量中获取许可,如果在指定的时间内获取成功,返回true,否则返回false
void release()释放一个许可,别忘了在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,达到动态扩展的效果,如:初始permits为1, 调用了两次release,最大许可会改变为2
int availablePermits()获取当前信号量可用的许可

Semaphore的使用:
Semaphore使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。

构造方法1:

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

permits 初始许可数,也就是最大访问线程数构造方法2:

public Semaphore(int permits, boolean fair) {
    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}

permits 初始许可数,也就是最大访问线程数

fair 当设置为false时,创建的信号量为非公平模式;当设置为true时,信号量是公平模式

JDK 非公平Semaphore实现:

1.使用一个参数的构造创建Semaphore对象时,会创建一个NonfairSync对象实例,并将state值设为传入的值(permits ),

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

NonfairSync间接的继承了AbstractQueuedSynchronizer实现

final static class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;
 
        NonfairSync(int permits) {
            super(permits);
        }
 
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
 
    Sync(int permits) {
        setState(permits);
    }

AbstractQueuedSynchronizer 的setState方法

protected final void setState(int newState) {
    state = newState;
}

2.调用tryAcquire方法时,实际是调用NonfairSync的nonfairTryAcquireShared方法,nonfairTryAcquireShared在父类Sync中实现,

public boolean tryAcquire() {
    return sync.nonfairTryAcquireShared(1) >= 0;
}

Sync的nonfairTryAcquireShared方法

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

nonfairTryAcquireShared 方法通过获取当前的state,以此state减去需要获取信号量的个数,作为剩余个数,如果结果小于0,返回此剩余的个数;如果结果大于等于0,则基于 CAS将state的值设置为剩余个数,当前步骤用到了for循环,所以只有在结果小于0或设置state值成功的情况下才会退出。如果返回的剩余许可个数大于0,tryAcquire方法则返回true;其余返回false。

AbstractQueuedSynchronizer的compareAndSetState方法,

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

3.release方法,释放一个许可

public void release() {
    sync.releaseShared(1);
}

AbstractQueuedSynchronizer的releaseShared方法,

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

release方法间接的调用了Sync的tryReleaseShared方法,该方法基于Cas 将state的值设置为state+1,一直循环确保CAS操作成功,成功后返回true。

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int p = getState();
        if (compareAndSetState(p, p + releases))
            return true;
    }
}

示例代码

    public static void main(String[] args) {
        final Semaphore semaphore = new Semaphore(2);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("线程:" + Thread.currentThread().getName() + "获得许可:" + index);
                    TimeUnit.SECONDS.sleep(1);
                    semaphore.release();
                    System.out.println("允许TASK个数:" + semaphore.availablePermits());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
public class ResourceManage {
    private final Semaphore semaphore ;
    private boolean resourceArray[];
    private final ReentrantLock lock;
    public ResourceManage() {
        //存放厕所状态
        this.resourceArray = new boolean[10];
        //控制10个共享资源的使用,使用先进先出的公平模式进行共享;公平模式的信号量,先来的先获得信号量
        this.semaphore = new Semaphore(10,true);
        //公平模式的锁,先来的先选
        this.lock = new ReentrantLock(true);
        for(int i=0 ;i<10; i++){
            //初始化为资源可用的情况
            resourceArray[i] = true;
        }
    }
    public void useResource(int userId) throws InterruptedException {
        semaphore.acquire();
        try{
            //占到一个坑
            int id = getResourceId();
            System.out.print("userId:"+userId+"正在使用资源,资源id:"+id+"\n");
            //do something,相当于于使用资源
            Thread.sleep(100);
            //退出这个坑
            resourceArray[id] = true;
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            //释放信号量,计数器加1
            semaphore.release();
        }
    }
    private int getResourceId(){
        int id = -1;
        lock.lock();
        try {
            //lock.lock();//虽然使用了锁控制同步,但由于只是简单的一个数组遍历,效率还是很高的,所以基本不影响性能。
            for(int i=0; i<10; i++){
                if(resourceArray[i]){
                    resourceArray[i] = false;
                    id = i;
                    break;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return id;
    }
}

class ResourceUser implements Runnable{
    private ResourceManage resourceManage;
    private int userId;
    public ResourceUser(ResourceManage resourceManage, int userId) {
        this.resourceManage = resourceManage;
        this.userId = userId;
    }
    @Override
    public void run(){
        System.out.print("userId:"+userId+"准备使用资源...\n");
        try {
            resourceManage.useResource(userId);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print("userId:"+userId+"使用资源完毕...\n");
    }

    public static void main(String[] args){
        ResourceManage resourceManage = new ResourceManage();
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            //创建多个资源使用者
            Thread thread = new Thread(new ResourceUser(resourceManage,i));
            threads[i] = thread;
        }
        for(int i = 0; i < 100; i++){
            Thread thread = threads[i];
            try {
                thread.start();//启动线程
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

文章参考资料
https://www.cnblogs.com/wxgblogs/p/5422508.html
https://blog.csdn.net/zbc1090549839/article/details/53389602

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值