Java并发编程--Semaphore

概述

  信号量(Semaphore)控制同时访问资源的线程数量,支持公平和非公平两种方式获取许可。

使用

  提供的方法

 1 public Semaphore(int permits)    //permits为许可数,默认非公平方式
 2 public Semaphore(int permits, boolean fair)
 3
 4 //获取一个许可。若获取成功,permits-1,直接返回;否则当前线程阻塞直到有permits被释放,除非线程被中断
 5 //如果线程被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。
 6 public void acquire() throws InterruptedException
 7 //忽略中断
 8 public void acquireUninterruptibly()
 9 //尝试获取一个许可,成功返回true,否则返回false。
10 //即使已将此信号量设置为使用公平排序策略,但是调用 tryAcquire() 也将 立即获取许可(如果有一个可用),而不管当前是否有正在等待的线程。
11 public boolean tryAcquire()
12 //超时尝试获取一个许可,该方法遵循公平设置
13 public boolean tryAcquire(long timeout, TimeUnit unit)
14 //释放一个许可
15 public void release()
16
17 //以上方法都是获取或释放一个许可,每个方法都存在对应的获取或释放指定个数许可的方法。例如public boolean tryAcquire(int permits)

  使用示例:

    使用信号量实现对内容池(例如线程池)的访问。

 1 class Pool {
 2     private static final int MAX_AVAILABLE = 100;    //许可数为100,在本例中也是内容池的item的个数。
 3     private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
 4
 5     //获取池中的一个item,首先从Semaphore获取许可;获取许可成功后,从池中获取一个可用的item,并把该item标记为已使用。
 6     public Object getItem() throws InterruptedException {
 7         available.acquire();
 8         return getNextAvailableItem();
 9     }
10
11     //将指定的item释放到池中,如果markAsUnused返回true,释放Semaphore的一个许可。
12     public void putItem(Object x) {
13         if (markAsUnused(x))
14             available.release();
15     }
16
17     // Not a particularly efficient data structure; just for demo
18
19     protected Object[] items = ... whatever kinds of items being managed    //内容池,例如:连接池,每个item代表一个连接。
20     protected boolean[] used = new boolean[MAX_AVAILABLE];    //标记池中的每个item是否已经被占用
21
22     protected synchronized Object getNextAvailableItem() {
23         for (int i = 0; i < MAX_AVAILABLE; ++i) {
24             if (!used[i]) {
25                 used[i] = true;
26                 return items[i];
27             }
28         }
29         return null; // not reached
30     }
31
32     protected synchronized boolean markAsUnused(Object item) {
33         for (int i = 0; i < MAX_AVAILABLE; ++i) {
34             if (item == items[i]) {
35                 if (used[i]) {
36                     used[i] = false;
37                     return true;
38                 } else
39                     return false;
40             }
41         }
42         return false;
43     }
44 }

实现原理

  基于AQS实现,用同步状态(state)表示许可数(permits),使用AQS的共享式获取和释放同步状态来实现permits的获取和释放。

  域

1 private final Sync sync;

    Sync是Semaphore的抽象内部类,继承了AQS。它有两个子类NonfairSync和FairSync,分别是非公平同步器和公平同步器。

    Sync的源码:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

    Sync(int permits) {
        setState(permits);
    }

    final int getPermits() {
        return getState();
    }

    //非公平共享式获取同步状态。自旋CAS获取同步状态直到成功或许可不足。
    //返回值语义:负数代表获取失败、0代表获取成功但没有剩余资源、正数代表获取成功,还有剩余资源。
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining)) //此处CAS时有可能同步队列中已有等待的线程,就导致的不公平性
                return remaining;
        }
    }

    //自定义共享式释放同步状态。自旋CAS释放同步状态直到成功,除非overflow
    //返回值语义:true表示成功,不可能释放失败,除非overflow
    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;
        }
    }

    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;
        }
    }

    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

    NonfairSync(非公平)源码:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

    FairSync(公平)源码:

 1 static final class FairSync extends Sync {
 2     private static final long serialVersionUID = 2014338818796000944L;
 3
 4     FairSync(int permits) {
 5         super(permits);
 6     }
 7     //自定义共享式释放同步状态。
 8     protected int tryAcquireShared(int acquires) {
 9         for (;;) {
10             //首先检查同步队列中是否有前驱。如果有则返回失败,将当前线程加入到同步队列的尾部,保证先尝试获取同步状态的线程先成功。
11             if (hasQueuedPredecessors())
12                 return -1;
13             int available = getState();
14             int remaining = available - acquires;
15             if (remaining < 0 ||
16                 compareAndSetState(available, remaining))
17                 return remaining;
18         }
19     }
20 }

  方法

    除tryAcquire外,都是通过调用AQS提供的方法实现获取失败时的阻塞和唤醒机制,具体策略建AQS的源码。

 1 //阻塞式获取一个许可,响应中断
 2 public void acquire() throws InterruptedException {
 3     sync.acquireSharedInterruptibly(1);    //调用AQS提供的可响应中断共享式获取同步状态方法
 4 }
 5
 6 //阻塞式获取一个许可,忽略中断
 7 public void acquireUninterruptibly() {
 8     sync.acquireShared(1);
 9 }
10
11 //非阻塞式获取一个许可
12 public boolean tryAcquire() {
13     return sync.nonfairTryAcquireShared(1) >= 0;
14 }
15
16 //释放一个许可
17 public void release() {
18     sync.releaseShared(1);
19 }

参考资料

  JDK DOC

  《Java并发编程的艺术》

时间: 2024-10-09 19:54:43

Java并发编程--Semaphore的相关文章

【Java并发编程实战】—–“J.U.C”:Semaphore

信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是独占锁.对于独占锁而言,它每次只能有一个线程持有,而共享锁则不同,它允许多个线程并行持有锁,并发访问共享资源. 独占锁它所采用的是一种悲观的加锁策略,  对于写而言为了避免冲突独占是必须的,但是对于读就没有必要了,因为它不会影响数据的一致性.如果某个只读线程获取独占锁,则其他读线程都只能等待了,这种情况下就限

Java并发编程:CountDownLatch、CyclicBarrier和Semaphore (总结)

下面对上面说的三个辅助类进行一个总结: 1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同: CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行: 而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行: 另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的. 2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权

Java并发编程小总结:CountDownLatch、CyclicBarrier和Semaphore

Java并发编程小总结:CountDownLatch.CyclicBarrier和Semaphore这几个类都是在JUC下,也就是java.util.concurrent包下.这两天学习了一下并发编程中的三个类的使用和一些应用场景,所以做一下记录和总结,方便自己日后再查看复现. 1.CountDownLatch.这个类的核心思想总结为8个字“秦灭6国,一统华夏”.它可以实现的是一个类似计数器的功能,与CyclicBarrier的思想正好相反.是一个减法操作.CountDownLatch有且只有一

Java并发编程笔记 并发概览

并发概览 >>同步 如何同步多个线程对共享资源的访问是多线程编程中最基本的问题之一.当多个线程并发访问共享数据时会出现数据处于计算中间状态或者不一致的问题,从而影响到程序的正确运行.我们通常把这种情况叫做竞争条件(race condition),把并发访问共享数据的代码叫做关键区域(critical section).同步就是使得多个线程顺序进入关键区域从而避免竞争条件的发生. >>线程安全性 编写线程安全的代码的核心是要对状态访问操作进行管理,尤其是对共享的和可变的状态访问. 线

JAVA并发编程J.U.C学习总结

前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www.cnblogs.com/chenpi/p/5614290.html 本文目录如下,基本上涵盖了J.U.C的主要内容: JSR 166及J.U.C Executor框架(线程池. Callable .Future) AbstractQueuedSynchronizer(AQS框架) Locks & C

【Java并发编程】并发编程大合集-值得收藏

http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅入深的学习顺序总结如下,点击相应的标题即可跳转到对应的文章    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [Java并发编程]守护线程和线程阻塞    [Ja

Java并发编程-总纲

Java 原生支持并发,基本的底层同步包括:synchronized,用来标示一个方法(普通,静态)或者一个块需要同步执行(某一时刻,只允许一个线程在执行代码块).volatile,用来标识一个变量是共享变量(线程不缓存),更新和读取是原子的.wait,线程等待某一个Object上的事件(notify事件,线程挂起,释放锁),需要在synchronized区中执行.notify,事件发生后,通知事件,通知一个挂起的线程,需要在synchronized区中执行.notifyAll,事件发生后,通知

《Java并发编程实战》第十六章 Java内存模型 读书笔记

Java内存模型是保障多线程安全的根基,这里仅仅是认识型的理解总结并未深入研究. 一.什么是内存模型,为什么需要它 Java内存模型(Java Memory Model)并发相关的安全发布,同步策略的规范.一致性等都来自于JMM. 1 平台的内存模型 在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏或栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证. JVM通过在适当的位置上插入内存栅栏来屏蔽在JVM与底层平台内存模型之间的

【Java并发编程实战】—–“J.U.C”:CountDownlatch

上篇博文([Java并发编程实战]-–"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一组线程互相等待,直到到达某个公共屏障点,才会进行后续任务".而CountDownlatch和它也有一点点相似之处:CountDownlatch所描述的是"在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待".在JDK API中是这样阐述的: 用给定的计数 初始化 Co