Semaphore源码解析(基于JDK8)

1 介绍

Semaphore 称为信号量,也是一个共享锁。通过一个内部类 Sync 继承 AQS,并重写了 tryAcquireSharedtryReleaseShared。共享锁具体原理可以见我 AQS 的第二篇。AQS(二)共享锁

必须给初始的资源个数 state。每次 acquire 都会减少 state,如果为 0,会堵塞;release 增加 state。

就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

简单使用如下。

Semaphore semaphore = new Semaphore(10,true);//初始有10个资源,true 表示公平锁
semaphore.acquire();//获取,暂时获取不到会堵塞
semaphore.release();//释放

如果资源只有一个,类似于 ReentrantLock,但和 ReentrantLock 也有区别。具体在于 ReentrantLock 总是可重入的,可以重入若干次;而 Semaphore 并不区分是当前线程重入获取资源还是其他线程获取资源,所以重入也会减少 state,state 为 1 的话,说明不可重入。

在实现 AQS 的方法方面,共享锁实现需要自旋,而独占锁不需要。

2 tryAcquireShared

对于公平锁和非公平锁该方法的实现是不同的,公平锁会先看一下队列中是否有等待的线程,是的话则失败,否则不断自旋,只要资源够,就能成功;非公平锁每次不会看前面是否有线程,而是直接抢。

// FairSync
protected int tryAcquireShared(int acquires) {
            for (;;) {
              	//公平锁在队列中不能有前面的节点,否则失败
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
              	// 有两种情况会返回,一种是资源不够,另一种是 CAS 成功了。
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }


// NonfairSync
protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
	//  Sync
final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
              	// 有两种情况会返回,一种是资源不够,另一种是 CAS 成功了。
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

3 tryReleaseShared

在不溢出的情况下,不断自旋执行 CAS,直到某次成功后返回 true。

protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
              	// 如果 releases 总是正的,出现下面的情况只可能是溢出
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

4 构造器和其他

// Semaphore
// 默认非公平锁
public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
// 第二个参数表示是否是公平锁。    
public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

// Sync 
// 自旋清空资源,并返回清空前的值
final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
// 和 tryAcquireShared 的区别是 next 可以为负数 
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;
            }
        }

5 使用

5.1 简单使用

下面这个例子中,资源只有两个,有10个线程,每个线程获取后会暂停5s然后释放,查看结果。

import java.util.concurrent.Semaphore;

public class B {
    public static Semaphore semaphore = null;
    public static void main(String[] args) throws Exception{
        semaphore = new Semaphore(2,true);
        Thread[] threads = new Thread[10];
        for(int i=0;i<threads.length;i++){
            threads[i] = new MyThread(semaphore,"线程"+i);
            threads[i].start();
        }
    }
}

class MyThread extends Thread{
    private Semaphore semaphore = null;
    public MyThread(Semaphore semaphore,String name){
        super(name);
        this.semaphore = semaphore;
    }
    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println(getName()+"已获取资源");
            Thread.sleep(5000);
        } catch(Exception e){
            e.printStackTrace();
        }finally {
            semaphore.release();
            System.out.println(getName()+"已释放资源");
        }
    }
}

5.2 尝试可重入

由于重入也会使用资源,所以在只有一个资源的情况下,重入是不可能的。

import java.util.concurrent.Semaphore;

public class C {
    public static Semaphore semaphore = null;
    public static void main(String[] args) throws Exception{
        semaphore = new Semaphore(1,true);
        Thread thread = new MyThread2(semaphore);
        thread.start();

    }
}

class MyThread2 extends Thread{
    private Semaphore semaphore = null;
    public MyThread2(Semaphore semaphore){
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire();
            enter();
        } catch(Exception e){
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
    }

    public void enter() {
        try {
          	// 会被堵塞在这一步,无法继续
            semaphore.acquire();
            System.out.println("可以重入");
        } catch(Exception e){
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
    }
}

5.3 直接释放

对于 Semaphore 来说,可以不执行 acquire 直接执行 release,就如下面的 testSemaphore,结果是 3.

对于 ReentrantLock,这显然不可能,必须先持有锁,才能释放。如果直接释放,testReentrantLock 会抛出 java.lang.IllegalMonitorStateException。

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

public class C {
    public static void main(String[] args) throws Exception{
        testReentrantLock();
        //testSemaphore();
    }
  	//没有异常,结果是3
    public static void testSemaphore(){
        Semaphore semaphore = new Semaphore(1,true);
        semaphore.release();
        semaphore.release();
        System.out.println(semaphore.availablePermits());
    }
  	//会抛出 java.lang.IllegalMonitorStateException
    public static void testReentrantLock(){
        ReentrantLock rt = new ReentrantLock();
        rt.unlock();
    }
}
posted @ 2021-04-18 16:38  Java与大数据进阶  阅读(73)  评论(0编辑  收藏  举报