基于AQS的前世今生,来学习并发工具类Semaphore。本文将从Semaphore的应用场景、源码原理解析来学习这个并发工具类。

1、 应用场景

  Semaphore用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。还可以用来实现某种资源池限制,或者对容器施加边界。

1.1   当成锁使用

  控制同时访问某个特定资源的操作数量,代码如下:

public class SemaphoreLock {
public static void main(String[] args) {
//1、信号量为1时 相当于普通的锁 信号量大于1时 共享锁
Output o = new Output();
for (int i = 0; i < 5; i++) {
new Thread(() -> o.output()).start();
}
}
}
class Output {
Semaphore semaphore = new Semaphore(1); public void output() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " start at " + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " stop at " + System.currentTimeMillis());
}catch(Exception e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
}

1.2   线程通信信号

  线程间通信,代码如下:

public class SemaphoreCommunication {
public static void main(String[] args) {
//2、线程间进行通信
Semaphore semaphore = new Semaphore(1);
new SendingThread(semaphore,"SendingThread");
new ReceivingThread(semaphore,"ReceivingThread");
}
}
class SendingThread extends Thread {
Semaphore semaphore;
String name; public SendingThread(Semaphore semaphore,String name) {
this.semaphore = semaphore;
this.name = name;
new Thread(this).start();
} public void run() {
try {
semaphore.acquire();
for (int i = 0; i < 5; i++) {
System.out.println(name + ":" + i);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
semaphore.release();
}
} class ReceivingThread extends Thread {
Semaphore semaphore;
String name; public ReceivingThread(Semaphore semaphore,String name) {
this.semaphore = semaphore;
this.name = name;
new Thread(this).start();
} public void run() {
try {
semaphore.acquire();
for (int i = 0; i < 5; i++) {
System.out.println(name + ":" + i);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
semaphore.release();
}
}

1.3   资源池限制

  对资源池进行资源限制,代码如下:

public class SemaphoreConnect {
public static void main(String[] args) throws Exception {
//3、模拟连接池数量限制
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 200; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
Connection.getInstance().connect();
}
});
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);
}
}
class Connection {
private static Connection instance = new Connection();
private Semaphore semaphores = new Semaphore(10,true);
private int connections = 0; private Connection() {
} public static Connection getInstance() {
return instance;
} public void connect() {
try {
semaphores.acquire();
doConnect();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphores.release();
}
} private void doConnect() {
synchronized (this) {
connections ++;
System.out.println("current get connections is : " + connections);
} try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} synchronized (this) {
connections --;
System.out.println("after release current connections is : " + connections);
}
}
}

1.4  容器边界限制

  对容器进行边界限制,代码如下:

public class SemaphoreBoundedList {
public static void main(String[] args) {
//4、容器边界限制
final BoundedList ba = new BoundedList(5);
Runnable runnable1 = new Runnable() {
public void run() {
try {
ba.add("John");
ba.add("Martin");
ba.add("Adam");
ba.add("Prince");
ba.add("Tod");
System.out.println("Available Permits : " + ba.getSemaphore().availablePermits());
ba.add("Tony");
System.out.println("Final list: " + ba.getArrayList());
}catch (InterruptedException ie) {
Thread.interrupted();
}
}
};
Runnable runnable2 = new Runnable() {
public void run() {
try {
System.out.println("Before removing elements: "+ ba.getArrayList());
Thread.sleep(5000);
ba.remove("Martin");
ba.remove("Adam");
}catch (InterruptedException ie) {
Thread.interrupted();
}
}
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
class BoundedList<T> {
private final Semaphore semaphore;
private List arrayList; BoundedList(int limit) {
this.arrayList = Collections.synchronizedList(new ArrayList());
this.semaphore = new Semaphore(limit);
} public boolean add(T t) throws InterruptedException {
boolean added = false;
semaphore.acquire();
try {
added = arrayList.add(t);
return added;
} finally {
if (!added)
semaphore.release();
} } public boolean remove(T t) {
boolean wasRemoved = arrayList.remove(t);
if (wasRemoved)
semaphore.release();
return wasRemoved;
} public void remove(int index) {
arrayList.remove(index);
semaphore.release();
} public List getArrayList() {
return arrayList;
} public Semaphore getSemaphore() {
return semaphore;
}
}

2、 源码原理解析

2.1 获取信号

  获取信号的方法如下:

public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);//共享式获取AQS的同步状态
}

  调用的是AQS的acquireSharedInterruptibly方法:

public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//线程中断 说明信号量对线程中断敏感
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //获取信号量失败 线程进入同步队列自旋等待
doAcquireSharedInterruptibly(arg);
}

  其中tryAcquireShared依赖的是Sync的实现,Sync提供了公平和非公平式的方式,先看非公平式。

protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();//同步状态 当前的信号量许可数
int remaining = available - acquires;//减去释放的信号量 剩余信号量许可数
if (remaining < 0 ||//剩余信号量小于0 直接返回remaining 不做CAS
compareAndSetState(available, remaining))//CAS更新
return remaining;
}
}

  再看下公平式的。

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

  最后来看下,如果未获取到信号量的处理方法doAcquireSharedInterruptibly。

private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);//线程进入同步队列
boolean failed = true;
try {
for (;;) {//自旋
final Node p = node.predecessor();
if (p == head) {//当前节点的前置节点是AQS的头节点 即自己是AQS同步队列的第一个节点
int r = tryAcquireShared(arg); //再去获取信号量
if (r >= 0) {//获取成功
setHeadAndPropagate(node, r);//退出自旋
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node); //获取失败 就取消获取
}
}

2.2 释放信号

  释放信号的方法如下:

public void release() {
sync.releaseShared(1);
}

  调用的是AQS的releaseShared方法:

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//释放信号量
doReleaseShared();//唤醒后续的线程节点
return true;
}
return false;
}

  tryReleaseShared交由子类Sync实现,代码如下:

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

  释放成功后,则继续调用doReleaseShared,唤醒后续线程节点可以来争取信号量了。

private void doReleaseShared() {
for (;;) {
Node h = head; //头节点
if (h != null && h != tail) {//同步队列中存在线程等待
int ws = h.waitStatus; //头节点线程状态
if (ws == Node.SIGNAL) {//头节点线程状态为SIGNAL 唤醒后续线程节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); //唤醒下个节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

  总结:Semaphore使用AQS同步状态来保存信号量的当前计数。它里面定义的acquireSharedInterruptibly方法会减少计数,当计数为非正值时阻塞线程,releaseShared方法会增加计数,在计数不超过信号量限制时要解除线程的阻塞。

参考资料:

https://github.com/lingjiango/ConcurrentProgramPractice

https://www.caveofprogramming.com/java-multithreading/java-multithreading-semaphores-part-12.html

https://java2blog.com/java-semaphore-example/

http://tutorials.jenkov.com/java-util-concurrent/semaphore.html

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

  1. Java并发编程Semaphore

    信号量 信号量类Semaphore,用来保护对唯一共享资源的访问.一个简单的打印队列,并发任务进行打印,加入信号量同时之能有一个线程进行打印任务 . import java.util.concurre ...

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

    信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...

  3. Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

    Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...

  4. Java并发编程的4个同步辅助类(CountDownLatch、CyclicBarrier、Semaphore、Phaser)

    我在<JDK1.5引入的concurrent包>中,曾经介绍过CountDownLatch.CyclicBarrier两个类,还给出了CountDownLatch的演示案例.这里再系统总结 ...

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

    下面对上面说的三个辅助类进行一个总结: 1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同: CountDownLatch一般用于某个线程A等待 ...

  6. 14、Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

    Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...

  7. 【转】Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

    Java并发编程:CountDownLatch.CyclicBarrier和Semaphore   Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在j ...

  8. java并发编程系列原理篇--JDK中的通信工具类Semaphore

    前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...

  9. Java并发编程基础三板斧之Semaphore

    引言 最近可以进行个税申报了,还没有申报的同学可以赶紧去试试哦.不过我反正是从上午到下午一直都没有成功的进行申报,一进行申报 就返回"当前访问人数过多,请稍后再试".为什么有些人就 ...

随机推荐

  1. 微信小程序中的app.js-清除缓存

    微信小程序中的app.js 关于小程序app.js生命周期的介绍 App(Object) App() 函数用来注册一个小程序.接受一个 Object 参数,其指定小程序的生命周期回调等. App() ...

  2. js连等赋值的陷阱

    先来看一段代码: (function(){ var x = y = 1; })(); console.log(y); console.log(x); 最开始我觉得这道题简直是送分题啊,很明显结果为,y ...

  3. Spring详解(八)------事务管理

    PS:本篇博客源码下载链接:http://pan.baidu.com/s/1mi3NhX2 密码:3io2 1.事务介绍 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指 ...

  4. VIM简单配置

    配置vim配置 编辑配置文件 feng@mint ~ $ vim ~/.vimrc 配置如下 主要配置为自动换行,设置行号,设置tab键为4个空格,同时将tab键自动转换成空格 set autoind ...

  5. tensorflow 1.0 学习:参数和特征的提取

    在tf中,参与训练的参数可用 tf.trainable_variables()提取出来,如: #取出所有参与训练的参数 params=tf.trainable_variables() print(&q ...

  6. Hadoop学习笔记(五):java开发MapReduce

    1. MapReduce的流程图(摘自马士兵老师视频),我们开发的就是其中的这两个(红框)过程.简述一下这个图,input就是我们需要处理的文件(datanode上文件的一个分块):Split就是将这 ...

  7. Oracle SQL 开发误区探索

    本文内容摘自<剑破冰山--Oracle开发艺术>一书. 1.避免对列运算 要善于通过等价改写消除 SQL 中对列的运算,这样可以避免索引无法使用. 2.消除隐式转换 3.关注空格(避免粗心 ...

  8. Magicodes.NET框架之路——让Magicodes.NET帮你编写代码

    时间总是过得很快,而我几乎没有时间来安安静静的写博客和完善文档.不过总算是框架在一直前进,而我的计划是在今年年底(公历)前,让此框架成熟稳定. 在很长一段时间里,我尝试了很多我之前没有接触的技术或者没 ...

  9. 精读JavaScript模式(八),JS类式继承

    一.前言 这篇开始主要介绍代码复用模式(原书中的第六章),任何一位有理想的开发者都不愿意将同样的逻辑代码重写多次,复用也是提升自己开发能力中重要的一环,所以本篇也将从“继承”开始,聊聊开发中的各种代码 ...

  10. MySQL:测试题

    一,表关系的练习测试 请创建如下表关系,并建立相关约束 一,创建表结构数据: 创建的话肯定先创建没有关联的表,老师,课程(关联老师),年级,班级(关联年级),学生(关联班级), 班级任职表 (关联老师 ...