Semaphore

简单使用

基本使用:信号量,用来限制能同时访问共享资源的线程上限,达到限流的效果。

示例:

public static void main(String[] args) {
        // 1. 创建 semaphore 对象
        Semaphore semaphore = new Semaphore(3);
        // 2. 10个线程同时运行
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                // 3. 获取许可
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    log.debug("running...");
                    sleep(1);
                    log.debug("end...");
                } finally {
                    // 4. 释放许可
                    semaphore.release();
                }
            }).start();
        }
    }

输出

07:35:15.485 c.TestSemaphore [Thread-2] - running... 
07:35:15.485 c.TestSemaphore [Thread-1] - running... 
07:35:15.485 c.TestSemaphore [Thread-0] - running... 
07:35:16.490 c.TestSemaphore [Thread-2] - end... 
07:35:16.490 c.TestSemaphore [Thread-0] - end... 
07:35:16.490 c.TestSemaphore [Thread-1] - end... 
07:35:16.490 c.TestSemaphore [Thread-3] - running... 
07:35:16.490 c.TestSemaphore [Thread-5] - running... 
07:35:16.490 c.TestSemaphore [Thread-4] - running... 
07:35:17.490 c.TestSemaphore [Thread-5] - end... 
07:35:17.490 c.TestSemaphore [Thread-4] - end... 
07:35:17.490 c.TestSemaphore [Thread-3] - end... 
07:35:17.490 c.TestSemaphore [Thread-6] - running... 
07:35:17.490 c.TestSemaphore [Thread-7] - running... 
07:35:17.490 c.TestSemaphore [Thread-9] - running... 
07:35:18.491 c.TestSemaphore [Thread-6] - end... 
07:35:18.491 c.TestSemaphore [Thread-7] - end... 
07:35:18.491 c.TestSemaphore [Thread-9] - end... 
07:35:18.491 c.TestSemaphore [Thread-8] - running... 
07:35:19.492 c.TestSemaphore [Thread-8] - end...

Semaphore还有一个构造函数Semaphore(int permits, boolean fair),fair表示是否创建公平或非公平的信号量

Semaphore应用

  • 使用Semaphore限流,在访问高峰期,让请求线程阻塞,高峰期过去再释放许可。它只适合单机线程数量,而不能限制资源数(例如连接数,请对比Tomcat LimitLatch的实现)

  • 用Semaphore实现简单连接池,对比享元模式下的实现(用wait notify),性能和可读性显然更好,注意下面的代码,线程数和数据库连接数是相同的

@Slf4j(topic = "c.Pool")
class Pool {
    // 1. 连接池大小
    private final int poolSize;

    // 2. 连接对象数组
    private Connection[] connections;

    // 3. 连接状态数组 0 表示空闲, 1 表示繁忙
    private AtomicIntegerArray states;

    private Semaphore semaphore;

    // 4. 构造方法初始化
    public Pool(int poolSize) {
        this.poolSize = poolSize;
        // 让许可数与资源数一致
        this.semaphore = new Semaphore(poolSize);
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MockConnection("连接" + (i+1));
        }
    }

    // 5. 借连接
    public Connection borrow() {// t1, t2, t3
        // 获取许可
        try {
            semaphore.acquire(); // 没有许可的线程,在此等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < poolSize; i++) {
            // 获取空闲连接
            if(states.get(i) == 0) {
                if (states.compareAndSet(i, 0, 1)) {
                    log.debug("borrow {}", connections[i]);
                    return connections[i];
                }
            }
        }
        // 不会执行到这里
        return null;
    }
    // 6. 归还连接
    public void free(Connection conn) {
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == conn) {
                states.set(i, 0);
                log.debug("free {}", conn);
                semaphore.release();
                break;
            }
        }
    }
}

Semaphore原理

在这里插入图片描述
核心源码分析:

   public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

非公平

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

代码逻辑:

  • 线程进来nonfairTryAcquireShared后,会进行自旋,获取当前的state(本例是3)
  • state减1,如果减去后的值小于0,意味加锁失败,如果大于0,就执行CAS操作试图修改state为之前减去后的值,因为修改state是并发的,所以这里用CAS保证线程安全

公平:

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

代码逻辑:整体上和非公平的一样,主要区别是在加锁前判队列中是否有阻塞的线程

如果加锁失败就执行下面方法

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //创建SHARED类型的节点,并加入同步队列中
        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;
                    }
                }
                //判断是否应该阻塞线程,第一次轮询的时候会把前去节点的waitStatus改为-1
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//park住线程
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

线程索取所失败会执行上述自旋过程,被加入到同步队列中,节点类型为SHARED,同时修改前驱节点的waitStatus为-1,并在第二次轮询的时候park再parkAndCheckInterrupt方法等待其他线程释放锁是unpark唤醒

在这里插入图片描述
在这里插入图片描述
核心代码分析:

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

释放锁的流程加锁的逆过程,state+1
释放锁成功后就执行下面方法

private void doReleaseShared() {
      
        for (;;) {
            Node h = head;//获取同步队列中的头节点
            if (h != null && h != tail) {
                int ws = h.waitStatus;  //获取头节点的waitStatus
                if (ws == Node.SIGNAL) {  //如果是-1,就执行下面的操作(有加锁失败可知,头节点已经被修改为-1)
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//先把头节点的waitStatus改为0,防止当前线程释放锁的时候其他线程干扰
                        continue;            // loop to recheck cases
                    unparkSuccessor(h); //唤醒头节点的后继结点,那之前阻塞在parkAndCheckInterrupt的后继节点对应的线程就会继续执行,尝试获取锁
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

在这里插入图片描述

同步队列中被唤醒的线程,如果获取锁失败,会继续阻塞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值