根据AQS推测Semaphore及源码分析

    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来实现自己需要的同步器。

你可能感兴趣的:(多线程,Java基础)