java并发编程之Semaphore原理分析与实例

  Semaphore、CountDownLatch、CyclicBarrier、Exchanger、Phaser均是concurrent包提供的用于多线程同步的对像,使用它们可以相对方便的处理复杂的同步场景。本博文介绍Semaphore的原理及实例

Semaphore

  实现了经典的信号量机制。信号量其实是一个非负整数表示可用共享资源的数目,主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。其内部是用一个计数器控制对共享资源的访问,计数据大于0资源允许访问,等于0是拒绝访问共享资源的,在访问共享资源前要先调用acquire方法,如果计数据大于0则可以访问资源,访问后要调用release方法释放共享资源,如果等于0则调用线程阻塞于此方法。
  分析下Semaphore类的源码,首先可以肯定一个简单的计数器是不能实现同步的,因为计数器本身的同步也是需要解决的,Semaphore是其通一个链式的queue来实现的AbstractQueuedSynchronizer,其中一个成员是private volatile int state;调用acquire方法会检查是否大于0,调用release方法会使state加1,AbstractQueuedSynchronizer其中有一个内部类Node,Node有一个成员Thread,这个成员就是存储调acquire阻塞时的线程,所以AbstractQueuedSynchronizer其实是一个包含计数器的,线程链式队列
  

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

  
  下面来看下构造函数,构造函数有两个,下面的是带有公平设置的。permits是共享资源的数目也可以理解成允许同时并行的线程数,fair是公平设置,可以理解成,如要为true,在调用aquire时先检查下队列中有没有等待的线程,有等待的线程就要进入队列等待,也就是保证了先到先得。如果为false,在调用aquire时是需要检查下队列中有没有等待的线程,也就是后来者可以先获得许可。

//不公平竞争
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
//公平竞争boolean fair=true
public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

使用实例:

/**
 * 其两个线程替执行count++,最终正确的结果应该是10,
 * 如果把Semampore的permits设为2或者不用Semaphore结果就是不可预知的,这种情况请自己偿试下
 */
public class SemaphoreDemo1 {

    private static int count = 0;//共享资源

    //一般情况下都是使用没有公平设置的Semaphore
    private static Semaphore smp = new Semaphore(1);

    public static void main(String[] args){
        //创建线程ThreadA并启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("ThreadA" + " 尝式获得许可");
                    smp.acquire();
                    System.out.println("ThreadA" + " 获得许可,开行访问共享资源");

                    // count++执行5次
                    for(int i=0; i < 5; i++) {
                        count++;
                        System.out.println("ThreadA" + ": " + count);
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException exc) {
                    System.out.println(exc);
                }
                smp.release();
            }

        },"ThreadA").start();

        //创建线程ThreadB并启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    System.out.println("ThreadB" + " 尝式获得许可");
                    smp.acquire();
                    System.out.println("ThreadB" + " 获得许可,开行访问共享资源");

                    // count++执行5次
                    for(int i=0; i < 5; i++) {
                        count++;
                        System.out.println("ThreadB" + ": " + count);
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException exc) {
                    System.out.println(exc);
                }
                smp.release();
            }

        },"ThreadB").start();
    }
}

执行如果如下:

ThreadA 尝式获得许可
ThreadA 获得许可,开行访问共享资源
ThreadA: 1
ThreadB 尝式获得许可
ThreadA: 2
ThreadA: 3
ThreadA: 4
ThreadA: 5
ThreadB 获得许可,开行访问共享资源
ThreadB: 6
ThreadB: 7
ThreadB: 8
ThreadB: 9
ThreadB: 10

Process finished with exit code 0
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿童木-atom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值