Java 并发 --- Semaphore源码分析
来源:互联网 发布:ubuntu删除文件 编辑:程序博客网 时间:2024/06/05 02:36
Semaphore 介绍(jdk 1.8)
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,可以用于做流量控制,特别是公共资源有限的应用场景,如果熟悉操作系统的概念,那么肯定对这么名词不陌生,当初在学习Linux进程通信中,也简单的学习过,今天再次接触到Semaphore。
Linux进程间通信——信号量
使用
Semaphore作为一种同步工具,使用是非常简单的。
public class SemaphoreDemo { private static Semaphore semaphore = new Semaphore(2); static class Task implements Runnable { private String name; public Task(String name) { this.name = name; } @Override public void run() { try { semaphore.acquire(); System.out.println(this.name+" start..."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(this.name+" end..."); semaphore.release(); } } } public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool(); pool.submit(new Task("thread1")); pool.submit(new Task("thread2")); pool.submit(new Task("thread3")); pool.submit(new Task("thread4")); pool.submit(new Task("thread5")); pool.submit(new Task("thread6")); pool.submit(new Task("thread7")); pool.submit(new Task("thread8")); pool.shutdown(); }}
Semaphore的使用和锁很类似,这是锁一般都是独占的,而Semaphore则是运行一定数量的线程同时访问,程序中我们设置的能同时访问的最大线程数是2,如果将该值设置为1,那么这个时候Semaphore 就退化成了锁了。
数据结构
如果对同步器比较熟悉,那么看到acquire,release 方法已经可以猜测Semaphore 是利用同步器来实现的了,既然用了同步器来实现,那么应该就不难分析了。
private final Sync sync;
Semaphore 中数据结构很简单,就通过内部类Sync 来实现的,而Sync则是依托同步器来实现的。
abstract static class Sync extends AbstractQueuedSynchronizer { Sync(int permits) { setState(permits); } ... // 省略其它方法}
在Java 并发 — CountDownLatch源码分析 中,我们对同步器做了简单的回顾,这里我们就不在详细阐述同步器的原理了,想要了解更多可以参考下面的内容:
Java 并发 —AbstractQueuedSynchronizer(同步器)-独占模式
Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition
同步器提供了模板方法,我们只需要重写部分方法,就可以实现我们自己的逻辑,因此分析Semaphore 自然而然也落到了这几个方法上。
构造方法
1、指定最大访问量
public Semaphore(int permits) { sync = new NonfairSync(permits); }
2、指定最大访问量和同步类型
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
通过指定permits 可以设置能同时访问资源的最大线程数,同时可以指定Semaphore 内部使用的同步工具的类型(公平锁或者非公平锁,默认是非公平锁),这个permits就是同步器的同步状态(也就是信号量)。
在 ReentrantLock 中就有公平锁和非公平锁之分,在Semaphore 中同样也存在。
抽象的Sync
abstract static class Sync extends AbstractQueuedSynchronizer { Sync(int permits) { setState(permits); //初始化同步状态 } final int getPermits() { return getState(); } //非公平的获取同步状态(直接参与同步状态的竞争) final int nonfairTryAcquireShared(int acquires) { for (;;) { //同步状态 int available = getState(); //新的同步状态 int remaining = available - acquires; //如果remaining<0 表明超出最大访问数量,失败 //如果remaining>=0 则cas 设置同步状态,获取同步状态成功。 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)) // cas 更新同步状态 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)) //cas 设置同步状态 return; } } //设置同步状态为0 final int drainPermits() { for (;;) { int current = getState(); if (current == 0 || compareAndSetState(current, 0)) return current; } }}
非公平的Sync
static final class NonfairSync extends Sync { // 初始化信号量(同步状态) NonfairSync(int permits) { super(permits); } //重新同步器 尝试获取同步状态 方法 protected int tryAcquireShared(int acquires) { // 调用非公平的获取同步器状态方法 return nonfairTryAcquireShared(acquires); }}
在同步器中我们已经知道了 只有前驱节点是头节点,才能够尝试获取同步状态,在公平锁中则遵循该规则,在非公平锁中,则不完全遵循该规则,当线程第一次获取同步状态时,不管同步队列中是否有等待线程,照样参与到竞争同步状态中,如果竞争失败,则可能会加入到同步队列中,在后续的获取同步状态过程中遵循FIFO规则,这个可以在同步器(AbstractQueuedSynchronizer)acquireQueued方法中可以得出,这个方法在前面有分析过。
公平的Sync
static final class FairSync extends Sync { // 初始化 同步状态 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; } }}
在同步器中我们已经知道了 只有前驱节点是头节点,才能够尝试获取同步状态,在公平锁中则遵循该规则,因此hasQueuedPredecessors 判断的是当前线程节点是否是头结点的后继节点,只有符合该规则那么才会获取同步状态。
这里的分析都很简单,因为已经很多次接触同步器了,前面也分析过多次这样的代码,因此这里就不再啰嗦了。
acquire 方法
1、可中断的获取信号量(同步状态)
public void acquire() throws InterruptedException { //获取一个同步状态(信号量) sync.acquireSharedInterruptibly(1); }
acquireSharedInterruptibly 是同步器中的方法,如果发生中断,会抛出中断异常
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 调用我们重写的尝试获取同步状态(信号量)的方法,如果获取失败,则进行自旋或加入同步队列。 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
2、 指定获取的信号量个数
public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits);}
在无参acquire中,默认是获取一个信号量,而这个方法,可以指定获取信号量的个数,如果发生中断,则抛出中断异常。
3、不响应中断的acquire
public void acquireUninterruptibly(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.acquireShared(permits); }
该方法不响应中断,也就是说上层应用没法通过中断的方法取消竞争,就算发生中断,线程同样会参与获取同步状态,获取失败则进行等待,如果发生过中断,会设置中断标志位,这样线程返回时,上层应用可以通过中断标志位来判断是否发生过中断。
release 方法
无参release
public void release() { sync.releaseShared(1);}
与acquire 相对,release则是释放同步状态(信号量)。releaseShared 是同步器中的方法,该方法会调用我们重写的tryReleaseShared 方法
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false;}
如果释放同步状态成功,则会执行doReleaseShared,这个也是同步器中的方法,会进行后续线程的唤醒工作(这个是共享模式的同步器)。
指定释放信号量的数量
public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits);}
这个acquire 类似,释放指定数量的信号量,本质上和无参的release 是一样的。
总结
Semaphore 是一种线程同步工具,使用的同步器来实现,可以控制同时访问特定资源的线程数量。
Semaphore 其实也是相当于一种锁,只是是一种共享锁罢了,我们已经看到了很多基于同步器实现的工具(ReentrantLock,ReentrantReadWriteLock,CountDownLatch),这些都是对同步器的一种应用,因此分析这些工具之前,必须要明白同步器的原理,这样就可以很容易明白这些工具的实现原理了。
Java 并发 — CountDownLatch源码分析
Java 并发 —ReentrantLock源码分析
Java 并发 —AbstractQueuedSynchronizer(同步器)-独占模式
Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition
- Java 并发 --- Semaphore源码分析
- Java并发之Semaphore的源码分析
- 《Java源码分析》:Semaphore
- 《Java源码分析》:Semaphore
- 【Java8源码分析】并发包-Semaphore
- 并发编程工具之三:Semaphore 用法及源码分析
- JAVA并发信号量 Semaphore
- Java并发同步器--Semaphore
- java并发-Semaphore
- Java并发之Semaphore
- Java并发之Semaphore
- java并发中的Semaphore
- java并发信号量Semaphore
- JUC - Semaphore 源码分析
- Semaphore 源码分析
- Java并发-AtomicInteger源码分析
- Java并发-ReentrantLock源码分析
- Java并发-ReentrantReadWriteLock源码分析
- 机器学习步骤和算法使用
- [bzoj1018]:[SHOI2008]堵塞的交通traffic
- 初探统计语言模型
- Linux设备驱动程序——驱动程序基本框架
- cpp: 小综合,读取文本中的数字与字符串
- Java 并发 --- Semaphore源码分析
- 游园不值
- Micorstation 二次开发 之 AddInManager
- JS基础(一)-------JS简介
- 鼠标单击的行反色,删除选定的行
- 深度学习环境搭建(Windows下Anaconda安装与配置)
- matlab中CRC的函数使用
- 欢迎使用CSDN-markdown编辑器
- R中关于包的操作