理解Java并发工具类Semaphore

时间:2022-06-11
本文章向大家介绍理解Java并发工具类Semaphore,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Semaphore是Java里面另外一个基本的并发工具包类,主要的的作用是用来保护共享资源的访问的,也就是仅仅允许一定数量的线程访问共享资源。Semaphore维护了有限数量的许可证,只有得到了许可证的线程才能进行共享资源的访问,如果得不到许可证,说明当前共享资源的访问已经达到最大限制,所以会挂起当前线程,直到前面的线程处理完任务之后,把许可证归还,后面排队的线程才有机会获取,然后处理任务。

这里面有两个注意点:

(1)大多数时候使用Semaphore都应该是公平模式,默认是非公平模式,如果需要公平模式可以在构造函数里面指定,公平性可以 保证先进先出,不会有线程饥饿问题出现,非公平模式,不保证顺序,吞吐量会更好一些。

(2)共享资源的访问,一般指的是读取,而不是更新,这里面不要做对共享变量的修改,除非你使用同步块来保证。

下面我们来看下Semaphore的构造方法:

Semaphore(int permits) //非公平模式指定最大允许访问许可证数量
Semaphore(int permits, boolean fair)//可以通过第二个参数控制是否使用公平模式

一些常用的方法:

acquire() //申请获取一个许可证,如果没有许可证,就阻塞直到能够获取或者被打断
availablePermits() // 返回当前有多少个有用的许可证数量hasQueuedThreads()//查询是否有线程正在等待获取许可证

drainPermits()//获得并返回所有立即可用的许可证数量
getQueuedThreads()//返回一个List包含当前可能正在阻塞队列里面所有线程对象
getQueueLength()//返回当前可能在阻塞获取许可证线程的数量
hasQueuedThreads()//查询是否有线程正在等待获取许可证
isFair()//返回是否为公平模式
reducePermits(int reduction)//减少指定数量的许可证
reducePermits(int reduction)//释放一个许可证
release(int permits)//释放指定数量的许可证
tryAcquire()//非阻塞的获取一个许可证

无论是Semaphore还是CountDonwLatch或者是CyclicBarrier,其实我们都可以通过Lock接口+Condition条件队列功能来模拟实现,但是不够抽象所以才出现了AQS这个抽象的面向开发者同步框架,比如这个Semaphore,我们看下如果使用Lock实现:

public class SemaphoreDemo2 {

    private final Lock lock=new ReentrantLock(true);
    private final Condition condition=lock.newCondition();
    private int permit;
    public SemaphoreDemo2(int permit) {
        this.permit=permit;
    }


    private void acquire(){

        lock.lock();
        try{
            if(permit==0){
                condition.await();//如果超过限制,就进入条件阻塞队列
            }
            System.out.println(Thread.currentThread().getName()+"  获得资源 .... ");
             permit--;

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }


    private void  release(){
        lock.lock();
        try{
            permit++;
            condition.signalAll(); //每当有一个释放令牌,就唤醒所有等待的线程
        }finally {
            lock.unlock();
        }


    }


}

下面我们看一下简单的使用例子:

Semaphore semaphore=new Semaphore(3);

        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 访问资源......");
                    semaphore.release();
            }
        };

        for (int i = 0; i < 5; i++) {
            Thread thread=new Thread(runnable);
            thread.start();
        }

        Thread.sleep(3000);

输出结果:

Thread-0 访问资源......
Thread-1 访问资源......
Thread-2 访问资源......
Thread-4 访问资源......
Thread-3 访问资源......

注意上面的例子只有3个许可证,我们运行了5个线程,所以同时最多只能运行3个线程,另外两个会阻塞直到前面的线程归还了许可证。

Semaphore底层原理:

Semaphore底层与CountDownLatch类似都是通过AQS的共享锁机制来实现的,指定的数量会设置到AQS里面的state里面,然后对于每一个 调用acquire方法线程,state都会减去一,如果state等于0,那么调用该方法的线程会被添加到同步队列里面,同时使用 LockSupport.park方法挂起等待,知道有线程调用了release方法,会对state加1,然后唤醒共享队列里面的线程,注意这里如果是 公平模式,就直接唤醒下一个等待线程即可,如果是非公平模式就允许新加入的线程与已有的线程进行竞争,谁先得到就是谁的,如果新加入的 竞争失败,就会走公平模式进入队列排队。

总结:

本文主要介绍了并发工具包Semaphore其主要作用来限制对于共享资源的访问,接着我们又介绍了其特点,使用及注意事项,然后又给出了使用其他同步工具Lock+Condition实现的Semaphore,最后又简单介绍了其底层实现原理。写作不易,烦请大伙可以点击下广告,多多支持一下。