并发编程工具之三:Semaphore 用法及源码分析
来源:互联网 发布:label mx标签打印软件 编辑:程序博客网 时间:2024/05/24 04:17
1. 信号量Semaphore的介绍
信号量可以看做对锁的扩展,我们知道的synchronized 和 重入锁 ReentrantLock ,一次只允许一个县城进行临界区资源访问,而信号量可以使多个线程同时访问某一资源。
如在营业厅办理手机卡业务,只有有限个窗口来同时办理业务。如果所有窗口都在办理业务中,那么这时过来办理业务的人需要等待,直到有人办理完并通知下一位时,这个人才可以开始自己的业务的办理。在这期间,这个人需要一直等待。在这里负责通知下一位人员继续办理业务的系统(或者人)就起到了信号量(非负数)的作用,表示公共资源(窗口)的可用数量,当一个线程要使用公共资源时,需要查看信号量是否大于0,如果大于0,则将其减1,并取占用窗口。如果为0,线程会阻塞,知道有其他线程释放资源。
我们先来熟悉一下这个类的主要功能性方法:
public Semaphore(int permits){...} public Semaphore(int permits, boolean fair) {...} // 第二个参数指定公平与否public void acquire() throws InterruptedException {...} // 获取资源,获取不到会一直阻塞public void acquireUninterruptibly() {...} // 同acquire(),但不会响应中断public boolean tryAcquire() {...} // 尝试获取资源,获取不到直接返回falsepublic boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {...} // 等待指定时间来获取资源,获取不到返回falsepublic void release() {...} // 释放资源public void acquire(int permits) throws InterruptedException {...} // 获取指定数量的资源,获取不到会一直阻塞public void acquireUninterruptibly(int permits) {...} // 同acquire(int permits),但不会响应中断public boolean tryAcquire(int permits) {...}public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException {...}public void release(int permits) {...}public int availablePermits() {...} // 返回可使用资源数量public int drainPermits() {...}protected void reducePermits(int reduction) {...}public boolean isFair() {...}public final boolean hasQueuedThreads() {...}public final int getQueueLength() {...}protected Collection<Thread> getQueuedThreads() {...}public String toString() {...}
使用最多的是方法acquire() 和release() 。
2. acquire() 和release()源码分析
在Java的并发包中,Semaphore类表示信号量。Semaphore内部主要通过AQS
(AbstractQueuedSynchronizer)实现线程的管理。Semaphore有两个构造函数,参数permits表示许可数,它最后传递给了AQS的state值。线程在运行时首先获取许可,如果成功,许可数就减1,线程运行,当线程运行结束就释放许可,许可数就加1。如果许可数为0,则获取失败,线程位于AQS的等待队列中,它会被其它释放许可的线程唤醒。在创建Semaphore对象的时候还可以指定它的公平性。一般常用非公平的信号量,非公平信号量是指在获取许可时先尝试获取许可,而不必关心是否已有需要获取许可的线程位于等待队列中,如果获取失败,才会入列。而公平的信号量在获取许可时首先要查看等待队列中是否已有线程,如果有则入列。
Semaphore内部类:
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1192457210091910933L; Sync(int permits) { setState(permits); } final int getPermits() { return getState(); } final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } 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; } } } /** * NonFair version */ 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); } } /** * Fair version */ static final class FairSync extends Sync { private static final long serialVersionUID = 2014338818796000944L; FairSync(int permits) { super(permits); } protected int tryAcquireShared(int acquires) { for (;;) { if (hasQueuedPredecessors()) return -1; int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } }
acquire源代码
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1);}public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg);}protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires);}final int nonfairTryAcquireShared(int acquires) { for (;;) { // 这里就是阻塞发生的地方 int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; }}
可以看出,如果remaining <0 即获取许可后,许可数小于0,则获取失败,在doAcquireSharedInterruptibly方法中线程会将自身阻塞,然后入列。
release源代码
public void release() { sync.releaseShared(1);}public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false;}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; }}
可以看出释放许可就是将AQS中state的值加1。然后通过doReleaseShared唤醒等待队列的第一个节点。可以看出Semaphore使用的是AQS的共享模式,等待队列中的第一个节点,如果第一个节点成功获取许可,又会唤醒下一个节点,以此类推。
3. 使用示例
示例一:
package somhu;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class SemaphoreTest implements Runnable{ private Semaphore semaphore = new Semaphore(5); @Override public void run() { try { semaphore.acquire(); Thread.sleep(2000); // 模拟耗时操作 System.err.println(Thread.currentThread().getName() + "--完成任务!"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService exe = Executors.newFixedThreadPool(20); SemaphoreTest task = new SemaphoreTest(); for (int i = 0; i < 20; i++) { exe.submit(task); } exe.shutdown(); }}
示例二:
package javalearning;import java.util.Random;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class SemaphoreDemo { private Semaphore smp = new Semaphore(3); private Random rnd = new Random(); class TaskDemo implements Runnable{ private String id; TaskDemo(String id){ this.id = id; } @Override public void run(){ try { smp.acquire(); System.out.println("Thread " + id + " is working"); Thread.sleep(rnd.nextInt(1000)); smp.release(); System.out.println("Thread " + id + " is over"); } catch (InterruptedException e) { } } } public static void main(String[] args){ SemaphoreDemo semaphoreDemo = new SemaphoreDemo(); //注意我创建的线程池类型, ExecutorService se = Executors.newCachedThreadPool(); se.submit(semaphoreDemo.new TaskDemo("a")); se.submit(semaphoreDemo.new TaskDemo("b")); se.submit(semaphoreDemo.new TaskDemo("c")); se.submit(semaphoreDemo.new TaskDemo("d")); se.submit(semaphoreDemo.new TaskDemo("e")); se.submit(semaphoreDemo.new TaskDemo("f")); se.shutdown(); }}
运行结果
Thread c is working
Thread b is working
Thread a is working
Thread c is over
Thread d is working
Thread b is over
Thread e is working
Thread a is over
Thread f is working
Thread d is over
Thread e is over
Thread f is over
可以看出,最多同时有三个线程并发执行,也可以认为有三个公共资源(比如计算机的三个串口)。
- 并发编程工具之三:Semaphore 用法及源码分析
- Java并发之Semaphore的源码分析
- Java 并发 --- Semaphore源码分析
- 并发编程之信号量Semaphore
- java并发编程之Semaphore
- java并发编程之Semaphore
- 【Java8源码分析】并发包-Semaphore
- Java并发编程中Semaphore的用法
- Java并发工具类之Semaphore
- 并发编程工具之二:CyclicBarrier 用法
- java并发编程之Semaphore整理
- java并发编程之Semaphore整理
- 根据AQS推测Semaphore及源码分析
- 译 -- Java 并发编程(多线程)三 | Semaphore | ThreadLocal | synchronized
- java并发之同步工具类二之信号灯semaphore
- Java并发编程:线程池创建及源码分析
- Java并发编程:ThreadPoolExecutor类及方法源码分析
- 多线程进阶之并发工具类第二篇:Semaphore、Exchanger
- struts2 搭建环境后出现 no action mapped for namespace 的解决方法
- 浅谈Java中的equals和==
- Struts2学习笔记(4)-ActionSupport类及Action接口详解
- Word2003入门动画教程46:用“碎片”复制或移动文章
- 一篇讲组播MAC和各类IP地址的文章
- 并发编程工具之三:Semaphore 用法及源码分析
- BASE理论。
- python上传字符和二进制的文件到PHP服务器
- 通信网络
- 11月23日
- 深度学习21天实战实战caffe学习笔记<19>
- 16、迭代子模式(Iterator)
- kettle7.0+ 创建资源库
- GoosBean