根据AQS推测Semaphore及源码分析

来源:互联网 发布:淘宝官方优惠券app 编辑:程序博客网 时间:2024/06/09 17:14

    Semaphore意为信号量,用法和CountDownLatch类似,也可以用来控制线程之间的协作关系,但通常用来控制同时访问的线程的数量。
    先看看示例:

public class SemaphoreTest {    public static void main(String[] args) {         // 线程池        ExecutorService exec = Executors.newCachedThreadPool();         // 只能5个线程同时访问        final Semaphore semp = new Semaphore(5);         // 模拟20个客户端访问        for (int index = 0; index < 20; index++) {            final int NO = index;             Runnable run = new Runnable() {                 public void run() {                     try {                         // 获取许可                        semp.acquire();                         System.out.println("Accessing: " + NO);                         Thread.sleep((long) (Math.random() * 10000));                         // 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印5条记录,之后线程一直阻塞                        semp.release();                     } catch (InterruptedException e) {                     }                 }             };             exec.execute(run);         }         // 退出线程池        exec.shutdown();     } }

    可以看到,在执行run方法里面的时候,需要先获取许可,只有获取之后才能接着执行,执行完之后再释放许可供后面的线程获取。
    其实,Semaphore也是继承的AQS,然后实现其中的共享模式(和CountDownLatch一样,因为他们都支持多个线程获取资源,而不像reentrankLock,同时只能有一个线程。)
    那么semp.acquire()方法是会阻塞线程的。而release()方法则会唤醒后继节点。
    再给出根据http://blog.csdn.net/FoolishAndStupid/article/details/75676027 中得到的简单的结论(只是简单的结论,实际上要复杂一些):

    我们推测:semp.acquire()方法是调用的AQS的acquireShared()方法,然后调用自定义同步器中实现的tryAcquireShared()方法,然后返回许可数量。当许可数量=0时表示没有资源了,则tryAcquireShared()返回<0,从而阻塞调用semp.acquire()的线程。
    再看semp.release()方法:推测它是调用的AQS的releaseShared()方法,然后调用自定义同步器中的实现的tryReleaseShared()方法,当资源数>0时,返回true,从而唤醒调用acquire()而阻塞的线程。

    给出推论之后再来看看源码:

    semp.acquire()方法:

public void acquire() throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }

    再来看看AQS中的acquireSharedInterruptibly()方法:

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

    其实就是调用的自定义同步器中实现的tryAcquireShared()方法。可以看到,semaphore中有一个公平的获取资源和非公平的获取资源2种:

公平的:protected int tryAcquireShared(int acquires) {            Thread current = Thread.currentThread();            for (;;) {                Thread first = getFirstQueuedThread();                if (first != null && first != current)                    return -1;                int available = getState();                int remaining = available - acquires;                if (remaining < 0 ||                    compareAndSetState(available, remaining))                    return remaining;            }        }非公平的:final int nonfairTryAcquireShared(int acquires) {            for (;;) {                int available = getState();                int remaining = available - acquires;                if (remaining < 0 ||                    compareAndSetState(available, remaining))                    return remaining;            }        }

    公平和非公平的获取资源的区别在于:非公平的方式会直接用CAS的方式去获取资源,然后返回剩余的资源数。而公平的方式则是先从等待队列中取出第一个线程,然后判断是否为当前线程,如果不是,就直接返回-1,然后将它加入到等待队列中末尾。公平的方式其实就是根据队列中等待的先后顺序来分配资源。
    还是符合我们图片中描述的。

    再来看看release()方法:

public void release() {        sync.releaseShared(1);    }

    AQS中的releaseShared():

public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {            doReleaseShared();            return true;        }        return false;    }

    就是自定义同步器中实现的tryReleaseShared()方法:
    不过这个并没有公平和非公平之分。因为释放资源都是将自身线程中的资源释放。和等待队列没关系。

protected final boolean tryReleaseShared(int releases) {            for (;;) {                int p = getState();                if (compareAndSetState(p, p + releases))                    return true;            }        }

    可以看到就使用for循环+CAS的方式,直到设置成功,则返回true,然后就可以唤醒后继节点。
    和图片中描述的相同

    所以只要搞清楚自定义同步器需要实现的接口和AQS中的关系,AQS为我们做了哪些步骤,那对于理解j.u.c中各个同步类具有很大的帮助,我们也能用AQS来实现自己需要的同步器。

原创粉丝点击