java并发编程JUC并发包之Semaphore

java并发编程JUC并发包之Semaphore


写在前面,JUC基础知识,详见 JUC并发包核心之AbstractQueuedSynchronizer
java并发编程面试必备,详见

  • java并发编程面试必备

概述

常用场景:限流
Semaphore也叫信号量,在JDK1.5被引入,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。
Semaphore内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。
访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。达到限流的目的
访问资源后,使用release释放许可。
Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略。

源码分析

类的结构,和ReentrantLock类似,参见java并发编程JUC并发包之ReentrantLock
在这里插入图片描述

构造器

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

acquire方法

都和ReentrantLock类似,只是state的处理不同,申请权限时,ReentrantLock是 +,Semaphore是 -

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }

非公平:

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

acquires值默认为1,表示尝试获取1个许可,remaining代表剩余的许可数。
如果remaining < 0,表示目前没有剩余的许可。
当前线程进入AQS中的doAcquireSharedInterruptibly方法等待可用许可并挂起,直到被唤醒。

公平:

protected int tryAcquireShared(int acquires) {
    for (;;) {
        if (hasQueuedPredecessors())
        	//同步队列有线程在排队,直接进入AQS中的doAcquireSharedInterruptibly方法等待可用许可并挂起,直到被唤醒。
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

acquires值默认为1,表示尝试获取1个许可,remaining代表剩余的许可数。
可以看到和非公平策略相比,就多了一个对阻塞队列的检查。
如果阻塞队列没有等待的线程,则参与许可的竞争。
否则直接插入到阻塞队列尾节点并挂起,等待被唤醒。

release方法

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

releases值默认为1,表示尝试释放1个许可,next 代表如果许可释放成功,可用许可的数量。
通过unsafe.compareAndSwapInt修改state的值,确保同一时刻只有一个线程可以释放成功。
许可释放成功,当前线程进入到AQS的doReleaseShared方法,唤醒队列中等待许可的线程。

使用方法

1、

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2,false);
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {

                try {
                    System.out.println(Thread.currentThread().getName() + " " + semaphore.getQueueLength());
                    semaphore.acquire();
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " " + semaphore.getQueueLength());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }

            }, String.valueOf(i));
            threads.add(t);
        }
        threads.parallelStream().forEach(Thread::start);
    }
}

执行结果:

5 0
6 0
3 0
7 1
2 2
0 3
1 4
8 5
4 6
9 7
5 8
6 7
3 6
7 5
2 4
0 3
1 2
8 1
4 0
9 0

2、模拟控制商场厕所的并发使用:

public class ResourceManageBySemaphore {
    private final Semaphore semaphore;
    private boolean resourceArray[];
    private final ReentrantLock lock;

    public ResourceManageBySemaphore() {
        this.resourceArray = new boolean[10];//存放厕所状态
        this.semaphore = new Semaphore(10, true);//控制10个共享资源的使用,使用先进先出的公平模式进行共享;公平模式的信号量,先来的先获得信号量
        this.lock = new ReentrantLock(true);//公平模式的锁,先来的先选
        for (int i = 0; i < 10; i++) {
            resourceArray[i] = true;//初始化为资源可用的情况
        }
    }

    public void useResource(int userId) {
        try {
            semaphore.acquire();
            int id = getResourceId();//占到一个坑
            System.out.print("userId:" + userId + "正在使用资源,资源id:" + id + "\n");
            Thread.sleep(100);//do something,相当于于使用资源
            resourceArray[id] = true;//退出这个坑
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();//释放信号量,计数器加1
        }
    }

    private int getResourceId() {
        int id = -1;
        lock.lock();//虽然使用了锁控制同步,但由于只是简单的一个数组遍历,效率还是很高的,所以基本不影响性能。
        try {
            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 ResourceManageBySemaphore resourceManage;
    private int userId;

    public ResourceUser(ResourceManageBySemaphore resourceManage, int userId) {
        this.resourceManage = resourceManage;
        this.userId = userId;
    }

    public void run() {
        System.out.print("userId:" + userId + "准备使用资源...\n");
        resourceManage.useResource(userId);
        System.out.print("userId:" + userId + "使用资源完毕...\n");
    }

    public static void main(String[] args) {
        ResourceManageBySemaphore resourceManage = new ResourceManageBySemaphore();
        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();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值