java多线程学习之【Semaphore】

目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。


前言

本文主要是讲解Semaphore的概念,基本用法,使用场景,底层代码原理剖析。

一、Semaphore是什么?

按照源码注释,翻译成中文,就是以下内容:

  • 计数信号量,从概念上讲,一个信号量维护一组许可。信号量存在许可,可以通过acquire获取它。每次release相当于增加一个许可。然而,没有实际的许可对象被使用。semaphore仅仅是保持可用的数量计数并采取相应行动。
  • 信号量通常是用来限制可以访问一些资源(物理或逻辑)的线程数量。
  • 使用一个信号量初始化,这样最多只有一个可用的许可证,可以作为一个互斥锁。这是通常被称为一个二进制信号量 ,因为它只有两个状态:一个可用的许可证,或零个可用的许可证。以这种方式使用时,二进制信号量拥有的特性:( 与java.util.concurrent.locks锁实现不同)可以由所有者以外的一个线程释放(如信号量没有所有权的概念)。这种特性在一些场景下,如死锁的复苏,是有用的。
  • 一般来说,信号用于控制资源访问应该初始化公平,确保没有线程访问不到资源。有时,吞吐量的考虑优于公平的考虑。

二、使用方法

Semaphore 提供了一些方法:

方法说明
acquire()从这个信号量获得一个许可,阻塞直到有一个可用,或线程被中断打断。
release()返回一个许可证给信号量。
acquire(int permits)permits个acquire() 。
release(int permits)permits个release()。
isFair()如果这个信号量是公平锁,返回true

acquire(int) , release(int) 两个方法支持同时获取,释放多个许河。需要知道风险:当没有使用公平锁时可能存在无限期推迟的情况。

三、应用场景

​信号量就是一个计数器,应用很广泛。

3.1有界队列

我们构建一个有界队列,在队列满的时候希望阻塞而不是中断。那么信号量的大小就是队列的边界。

代码如下

package com.valley.juc.tools.semaphore;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;

/**
 * @author valley
 * @date 2022/6/17
 * @Description TODO
 */
public class BoundedHashSet<T> {

    private final Set<T> set;
    private final Semaphore sem;


    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InterruptedException, NoSuchMethodException, InvocationTargetException {
        String name =BoundedHashSet.class.getName();
        Class<?> clazz=Class.forName(name);
        Constructor constructor = clazz.getConstructor(int.class);
        BoundedHashSet boundedHashSet= (BoundedHashSet) constructor.newInstance(100);
        boundedHashSet.add(13);
        boundedHashSet.remove(13);
    }

    public BoundedHashSet(int bound) {
        // 初始化信号量的值,作为set容器的边界
        this.set = Collections.synchronizedSet(new HashSet<T>());
        this.sem = new Semaphore(bound);
    }

    /**
     * 如果添加成功,则信号量的可用值减少一个;否则,被acquire占用的信号量要释放。
     * @param o
     * @return
     * @throws InterruptedException
     */
    public boolean add(T o) throws InterruptedException{
        sem.acquire();
        boolean wasAdded = false;

        try{
            wasAdded = set.add(o);
            return wasAdded;
        } finally {
            if (!wasAdded) {
                sem.release();
            }
        }
    }

    /**
     * 容器删除一个数据是,要释放对应的信号量
     * @param o
     * @return
     */
    public boolean remove(Object o){
        final boolean remove = set.remove(o);

        if (remove) {
            sem.release();
        }

        return remove;
    }

}

3.2资源池

注意,调用acquire时,为了不影响资源被返回到池中,没有同步锁持有。但是封装的信号同步访问池需要限制,独立于任何同步需要保持池本身的一致性。synchronized修饰两个方法,共用一把锁,保持了池本身的一致性。

package com.valley.juc.tools.semaphore;

import java.util.concurrent.Semaphore;

/**
 * @author valley
 * @date 2022/6/17
 * @Description TODO
 */
public class Pool {

    private static final int MAX_AVAILABLE = 10;
    private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);


    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Pool pool =Pool.class.newInstance();
        try {
            System.out.println(pool.getItem());
            pool.putItem(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public Object getItem() throws InterruptedException {
      available.acquire();
      return getNextAvailableItem();
    }

    public void putItem(Object x) {
      if (markAsUnused(x))
        available.release();
    }

    // Not a particularly efficient data structure; just for demo
    protected Object[] items = new Object[]{1,2,3,4,5,6,7,8,9,10};
    protected boolean[] used = new boolean[MAX_AVAILABLE];

    protected synchronized Object getNextAvailableItem() {
      for (int i = 0; i < MAX_AVAILABLE; ++i) {
        if (!used[i]) {
           used[i] = true;
          return items[i];
        }
      }
      return null; // not reached
    }

    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;
    }


}

四、源码剖析

Semaphore 的源码在JUC并发工具中,相对复杂点:

  • 使用了继承AQS的静态内部类Sync,也就是底层基于AbstractQueuedSynchronizer 来实现的
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

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

        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;
            }
        }

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

    /**
     * NonFair version
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

    /**
     * Fair version
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

  • Semaphore 构造函数中包含指定许可的数量和是否是公平锁;
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
  • acquire()
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    /**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • release

    public void release() {
        sync.releaseShared(1);
    }
private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
 

五、和其他工具比较

  • Semaphore,CountDownLatch与CyclicBarrier区别

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,Semaphore主要是计数。


总结

主要讲解Semaphore,后面重点讲AQS。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Semaphore是一个用于控制同时访问特定资源的线程数量的工具类。它可以用于限制同一时间内同时运行的线程数量,从而达到限流的效果。 Semaphore的构造函数有两个参数,第一个参数是许可数量,即允许同时访问的线程数量,第二个参数是公平性标志,如果设置为true,表示线程获取许可的顺序是按照线程调用acquire方法的顺序来确定的,否则是随机的。 Semaphore有两个主要方法: - acquire():获取一个许可,并减少许可数量,如果许可数量为0,则当前线程阻塞等待其他线程归还许可; - release():归还一个许可,并增加许可数量,如果有等待的线程,则其中一个线程会获取到许可。 代码示例: ```java import java.util.concurrent.Semaphore; public class SemaphoreExample { public static void main(String[] args) { int threadCount = 10; Semaphore semaphore = new Semaphore(5); // 设置最多允许5个线程同时运行 for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { semaphore.acquire(); // 获取许可 System.out.println(Thread.currentThread().getName() + "开始执行"); Thread.sleep(1000); // 模拟执行 System.out.println(Thread.currentThread().getName() + "执行完毕"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 释放许可 } }).start(); } } } ``` 解释: 上述代码中设置了最多允许5个线程同时运行,而实际创建了10个线程。在执行过程中,每个线程都会先去获取一个许可,只有获取到许可后才能执行其它操作。如果许可数量已经为0,则线程会进入阻塞等待状态,直到其它线程释放许可。当线程执行完毕后,会释放占用的许可,让其他线程可以继续执行。这样可以有效控制同时执行的线程数量,从而达到限流的效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

blackoon88

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值