JDK1.8并发包之--Semaphore

AQS是JDK并发包的基础,2018年研究了一番源码,略懂,近日再阅,又有提高。AQS的派生类有很多,本文介绍Semaphore,先从类注释说起。

    1. 第一段
A counting semaphore.  Conceptually, a semaphore maintains a set of permits. 
 Each {@link #acquire} blocks  if necessary until a permit is available, and then
 takes it.  Each {@link #release} adds a permit, potentially  releasing a blocking
 acquirer.  However, no actual permit objects are used; the {@code Semaphore} 
just  keeps a count of the number available and acts accordingly.

注释说,Semaphore管理的是一组许可证,acquire方法将获取一个许可证,如果池中还有许可证,则获取成功并持有该许可证,否则阻塞。release方法向池中添加一个许可证,释放成功,acquire方法阻塞的线程就会唤醒并获得许可证。实际上许可证并不特指某些对象,而是由一个整形数字扮演许可证的角色。

    1. 第二段
Semaphores are often used to restrict the number of threads than can access
 some (physical or logical)  resource. For example, here is a class that uses a
 semaphore to control access to a pool of items:

这段话讲明Semaphore的用处,常用来限制线程访问共享资源的数量,且注释中给出了一个源码例子:

    class Pool {
        // 池中允许的许可证数量
        private static final int MAX_AVAILABLE = 100;
        // 实例化Semaphore
        private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

        public Object getItem() throws InterruptedException {
            available.acquire();
            return getNextAvailableItem();
        }

        public void putItem(Object x) {
            if (markAsUnused(x)) {
                available.release();
            }
        }

        // Not a particularly efficient data structure; just for demo

        protected Object[] items = new Object[MAX_AVAILABLE];
        protected boolean[] used = new boolean[MAX_AVAILABLE];

        protected synchronized Object getNextAvailableItem() {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (!used[i]) {
                    used[i] = true;
                    return items[i];
                }
            }
            return null;
        }

        protected synchronized boolean markAsUnused(Object item) {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (item == items[i]) {
                    if (used[i]) {
                        used[i] = false;
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
    }
    1. 第三段
Before obtaining an item each thread must acquire a permit from  
the semaphore, guaranteeing that an item is available for use. When 
 the thread has finished with the item it is returned back to the  pool 
and a permit is returned to the semaphore, allowing another  thread 
to acquire that item.  Note that no synchronization lock is  held when 
{@link #acquire} is called as that would prevent an item  from being 
returned to the pool.The semaphore encapsulates the  synchronization 
needed to restrict access to the pool, separately  from any synchronization 
needed to maintain the consistency of the  pool itself.

上文讲述例子的含义:

线程从Semaphore中获取许可证才能获得一个item,前提是Semaphore中
必须有许可证存在。线程使用完item后,先标记item未被使用,再向Semaphore
归还许可证,这样其他线程就能再次利用这个许可证。值得注意的是,线程执行
acquire方法并没有使用同步器,这么做的好处是避免获取许可证时无法归还许可
证。因为线程同步只允许一个线程获得锁。实际上Semaphore基于AQS,封装了
同步原语,目的是限制线程访问资源的数量,剥离了同步锁,实现池的一致性。
    1. 第四段
A semaphore initialized to one, and which is used such that it  only has at most
 one permit available, can serve as a mutual  exclusion lock.  This is more commonly
 known as a <em>binary  semaphore</em>, because it only has two states: one 
permit  available, or zero permits available.  When used in this way, the  binary 
semaphore has the property (unlike many {@link java.util.concurrent.locks.Lock} 
 implementations), that the &quot;lock&quot; can be released by a  thread other 
than the owner (as semaphores have no notion of  ownership).  This can be useful 
in some specialized contexts, such  as deadlock recovery.

这段话告诉我们,Semaphore的特殊用法,当许可证数量赋值为1时,许可证只有两个状态,1代表可用,0代表不可用,这样的Semaphore叫二进制信号量,性质跟Lock接口的实现类很像,如ReentrantLock可重入锁。但是又有区别,互斥锁要求必须同一个线程获取锁,同一个线程释放锁,二进制信号量允许一个线程获取锁,另一个线程释放锁,这样特性常用在死锁恢复中。代码案例如下:

public class WaitNotifyAndLockSupport {
    private static final Object obj = new Object();

    public static void main(String[] args) {
        Executor executor = Executors.newCachedThreadPool();
        Pool pool = new Pool();
        for (int i = 0; i < 10; i++) {
            executor.execute(new Student(i+1, pool));
        }

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        pool.releaseTrack();

    }


    static class Pool {
        private static final int MAX_AVAILABLE = 1;
        private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
        protected Track[] items = {new Track(1)};

        protected boolean[] used = new boolean[MAX_AVAILABLE];

        public Track getTrack() throws InterruptedException {
            available.acquire();
            return getNextAvailableItem();
        }

        public void releaseTrack(Object x) {
            if (markAsUnused(x)) {
                available.release();
            }
        }

        public void releaseTrack() {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                used[i] = false;
            }
            available.release();
        }


        protected synchronized Track getNextAvailableItem() {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (!used[i]) {
                    used[i] = true;
                    return items[i];
                }
            }
            return null;
        }

        protected synchronized boolean markAsUnused(Object item) {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (item == items[i]) {
                    if (used[i]) {
                        used[i] = false;
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
    }

    static class Track {
        private int num;

        public Track(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Track{" +
                    "num=" + num +
                    '}';
        }
    }

    static class Student implements Runnable {

        private int num;
        private Pool pool;

        public Student(int num, Pool pool) {
            this.num = num;
            this.pool = pool;
        }

        @Override
        public void run() {

            try {
                //获取跑道
                Track track = pool.getTrack();
                if (track != null) {
                    System.out.println("学生" + num + "在" + track.toString() + "上跑步");
                    TimeUnit.SECONDS.sleep(2);
//                    System.out.println("学生" + num + "释放" + track.toString());
                    //释放跑道
//                    pool.releaseTrack(track);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

运行结果:

学生1在Track{num=1}上跑步
学生3在Track{num=1}上跑步

线程Student 如果不释放许可证,在main主线程中释放许可证,则等待的10个线程中会有一个线程获得许可证。

    1. 第五段
The constructor for this class optionally accepts a  <em>fairness</em> 
parameter. When set false, this class makes no  guarantees about the
 order in which threads acquire permits. In  particular, <em>barging</em>
 is permitted, that is, a thread  invoking {@link #acquire} can be allocated 
a permit ahead of a  thread that has been waiting - logically the new thread
 places itself at  the head of the queue of waiting threads. When fairness is
 set true, the  semaphore guarantees that threads invoking any of the {@link
  #acquire() acquire} methods are selected to obtain permits in the order in  
which their invocation of those methods was processed (first-in-first-out; FIFO).
 Note that FIFO ordering necessarily  applies to specific internal points of 
execution within these  methods.  So, it is possible for one thread to invoke  
{@code acquire} before another, but reach the ordering point after  the other, 
and similarly upon return from the method.  Also note that the untimed {@link
 #tryAcquire() tryAcquire} methods do not  honor the fairness setting, but will
 take any permits that are  available.

这段话很长,将的内容却很简单,说的是公平锁和非公平锁,公平锁遵循FIFO原则,先从AQS队列头部唤醒阻塞的线程,非公平锁并不遵循这一原则,如果刚好有线程释放许可证,那么来的找不如来的巧的线程将获得许可证。

    1. 第六段
Generally, semaphores used to control resource access should be  
initialized as fair, to ensure that no thread is starved out from  accessing
 a resource. When using semaphores for other kinds of  synchronization 
control, the throughput advantages of non-fair  ordering often outweigh 
fairness considerations.

这段话说的是公平锁、非公平锁的选择,公平锁保证等久的线程线程不会空等,公平锁释保证吞吐量,保证许可证的高可利用率。

    1. 第七段
This class also provides convenience methods to {@link  #acquire(int) 
acquire} and {@link #release(int) release} multiple  permits at a time.  
Beware of the increased risk of indefinite  postponement when these 
methods are used without fairness set true.

最后一段话告诉我们,acquire和release支持同一时间使用,此时若采用非公平锁,那么线程可能无限期阻塞。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容