Semaphore,等待指定数量的线程完成任务即可
public class A { private static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 同步关键类,构造方法传入的数字是多少,则同一个时刻,只运行多少个进程同时运行制定代码 private Semaphore semaphore = new Semaphore(2); public static void main(String[] args) throws Exception{ A a = new A(); for (int i = 0; i < 10; i++) { new Thread(()->{ a.doSomething(); }).start(); } } public void doSomething() { try { /** * 在 semaphore.acquire() 和 semaphore.release()之间的代码,同一时刻只允许制定个数的线程进入, * 因为semaphore的构造方法是1,则同一时刻只允许一个线程进入,其他线程只能等待。 * */ semaphore.acquire(); System.out.println(Thread.currentThread().getName() + ": start-" + getFormatTimeStr()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + ": end-" + getFormatTimeStr()); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } public static String getFormatTimeStr() { return sf.format(new Date()); } }
初始化
初始化的时候,就是将 permits 设置给 state,标记占用锁标识。state的值就代表当前所剩余的令牌数量。
acquire()
获取令牌
在 tryAcquireShared() 里面,第一个线程会先获取到当前的令牌数量,我们初始化的时候是2,remaining = 2-1>0 ,所以走cas操作赋值,等到第三个线程来的时候,则 remaining = 0-1>0,此时第三个线程就没获取到令牌了,走下面的 doAcquireSharedInterruptibly() 方法。
挂起线程
获取到令牌的线程就继续执行它的业务逻辑了,没有获取到令牌的线程就会来到这里。
1. addWaiter(Node.SHARED); 首先创建节点加入阻塞队列
2. node.predecessor(); 获取到上一个节点p,如果上一个节点是头节点,说明自己排在第一个。
3. 如果自己是第一个线程就 tryAcquireShared() 尝试cas获取令牌。(因为可能走到这里时,别人释放了令牌啊)
4. 然后 r>=0 就获取到了令牌,那就把当前线程从对等队列里面摘取出来
5. 如果不是第一个排队的节点,那就在 parkAndCheckInterrupt() 里面通过 park() 操作把自己挂起来,等待其他线程来把它唤醒
release()
释放令牌
在 tryReleaseShared() 里面将 state 加一,然后cas操作执行成功就去唤醒等待队列中的节点。
唤醒其他节点
1. 判断头节点 h 的状态 Node.SIGNAL 是否需要唤醒后继节点
2. 进行 cas 操作修改状态为初始0
3. unparkSuccessor(h) 唤醒 h.next 节点
。