一、使用场景
主要是用来控制同时执行线程的数量,用以保护临界资源
二、使用实例
package com.test.lock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemphoreTest4 { static Logger logger = LoggerFactory.getLogger(SemphoreTest4.class ) ; public static void main(String[] args) throws InterruptedException { Semaphore semphore = new Semaphore(2); for (int i = 1; i <=29 ; i++) { new Thread(()->{ try { logger.info(String.format("t %s准备完成", Thread.currentThread().getName() )); semphore.acquire(); logger.info(String.format(" 线程 %s 正在执行。。。。。", Thread.currentThread().getName())); TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } semphore.release(); logger.info(String.format("t %s 执行结束", Thread.currentThread().getName())); },"t"+(i+20)).start(); ; } } }
三、源码主要方法解读
1)构造方法
public Semaphore(int permits) { //设置AQS中status的值 sync = new NonfairSync(permits); }
2)semphore.acquire();
public void acquire() throws InterruptedException { //当前线程获取执行令牌 sync.acquireSharedInterruptibly(1); }
3) 可以执行还是需要去排队
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //获取执行权限 doAcquireSharedInterruptibly(arg); //排队 }
4)尝试获取执行权限
protected int tryAcquireShared(int acquires) { for (;;) { //如果队列里面已经有排队的,则直接返回-1 去排队 if (hasQueuedPredecessors()) return -1; //获取当前的状态 int available = getState(); //计算剩余的许可数量 int remaining = available - acquires; if (remaining < 0 || //如果<0 则返回去排队 compareAndSetState(available, remaining)) //如果获取许可 成功,则返回去执行 return remaining; } }
5)线程执行完成,则释放许可
//循环+CAS数据最终的一致性 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; } }
6) 释放许可以后需要唤醒下面线程
private void doReleaseShared() { 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; } }
四、整体思路是:
1 设置标识位
2 线程过来获取许可,如果可以获取到,则继续执行
3 如果获取失败,则进入队列等待,直到被唤醒
4 释放许可,并唤醒后面线程