Semaphore用于保存当前可用许可的数量。是通过共享锁实现的。根据共享锁的获取原则,Semaphore分为"公平信号量"和"非公平信号量"。

"公平信号量"和"非公平信号量"的释放信号量的机制是一样的!不同的是它们获取信号量的机制:线程在尝试获取信号量许可时,对于公平信号量而言,如果当前线程不在队列的头部,则排队等候;而对于非公平信号量而言,无论当前线程是不是在队列的头部,它都会直接获取信号量。该差异具体的体现在,它们的tryAcquireShared()函数的实现不同。

如果要使用Semaphore对象时,首先通过构造函数取得对象,如下:

public Semaphore(int permits) {  // 构造函数默认使用非公平的方式获取
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) { // 构造函数中指定获取的方式
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

然后就可以调用Semaphore对象进行信号量的获取了,如下:

public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}

1、公平信号量的获取

首先来看公平信号量的获取,方法如下:

public final void acquireSharedInterruptibly(int arg)   throws InterruptedException {
// 如果线程是中断状态,则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取共享锁;获取成功则直接返回,获取失败,则通过doAcquireSharedInterruptibly()获取。
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

如果tryAcquireShared()方法获取失败,通常会返回一个小于0的数量。Semaphore中公平锁对应的tryAcquireShared()实现如下:

protected int tryAcquireShared(int acquires) {
for (;;) {
// 判断当前线程是不是队列中的第一个线程。若是的话,则返回-1,跳出死循环
if (hasQueuedPredecessors())
return -1;
int available = getState(); // 获取当前可用的信号量的许可数
// 设置获得acquires个信号量许可之后,剩余的信号量许可数
int remaining = available - acquires;
// 如果剩余的信号量许可数>=0”,则设置可以获得的信号量许可数为remaining。
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}

返回的是remaining,如果为-1,表示获取失败。如果为>=0,则预示了其他共享获取操作能否成功。

如上方法获取失败后,调用doAcquireSharedInterruptibly()方法,源码如下:

private void doAcquireSharedInterruptibly(long arg)   throws InterruptedException {
// 创建当前线程的Node节点,且Node中记录的锁是共享锁类型;并将该节点添加到队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取上一个节点,如果上一节点是队列的表头,则尝试获取共享锁
final Node p = node.predecessor();
if (p == head) {
long 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、非公平信号量的获取

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))// 如果remaining>=0,则通过CAS方式更新当前许可的数量
return remaining;
}
}

判断当前剩余的信号量许可数,返回小于0的数表示获取失败,大于等于0表示成功。

非公平和公平主要体现在tryAcquireShared()方法的实现上。

(1)非公平获取 如果当前可用的信号量许可大于等于请求数,则通过CAS修改剩余许可量并返回,如果小于的话,返回小于0的数,表示获取失败。

(2)公平获取 在获取时还会判断。如果当前线程不在队列的头部,则返回-1,排队等候;然后再去判断信号量许可。

3、公平信号量的释放

调用如下方法来释放信号量许可,如下:

public void release() {
sync.releaseShared(1);
} public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}

调用无参数的release()方法默认只释放一个信号量许可,而下面的可以指定:

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

首先调用tryReleaseShared()方法去释放,源代码如下:

protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获取可以获得的信号量的许可数
int current = getState();
// 获取释放releases个信号量许可之后,剩余的信号量许可数
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
// 设置可以获得的信号量的许可数为next
if (compareAndSetState(current, next))
return true;
}
}

如果tryReleaseShared()尝试释放共享锁失败,则会调用doReleaseShared()去释放共享锁。doReleaseShared()的源码如下

private void doReleaseShared() {
for (;;) {
// 获取CLH队列的头节点
Node h = head;
// 如果头节点不为null,并且头节点不等于tail节点。
if (h != null && h != tail) {
// 获取头节点对应的线程的状态
int ws = h.waitStatus;
// 如果头节点对应的线程是SIGNAL状态,则意味着“头节点的下一个节点所对应的线程”需要被unpark唤醒。
if (ws == Node.SIGNAL) {
// 设置“头节点对应的线程状态”为空状态。失败的话,则继续循环。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒“头节点的下一个节点所对应的线程”。
unparkSuccessor(h);
}
// 如果头节点对应的线程是空状态,则设置“文件点对应的线程所拥有的共享锁”为其它线程获取锁的空状态。
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果头节点发生变化,则继续循环。否则,退出循环。
if (h == head) // loop if head changed
break;
}
}

4、非公平信号量的释放

protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 设置可以获得的信号量的许可数
int available = getState();
// 设置获得acquires个信号量许可之后,剩余的信号量许可数
int remaining = available - acquires;
// 如果剩余的信号量许可数>=0,则设置可以获得的信号量许可数为remaining
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

举个例子,如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; public class SemaphoreTest1 {
private static final int SEM_MAX = 10;
public static void main(String[] args) {
Semaphore sem = new Semaphore(SEM_MAX);
//创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
//在线程池中执行任务
threadPool.execute(new MyThread(sem, 5));
threadPool.execute(new MyThread(sem, 4));
threadPool.execute(new MyThread(sem, 7));
//关闭池
threadPool.shutdown();
}
} class MyThread extends Thread {
private volatile Semaphore sem; // 信号量
private int count; // 申请信号量的大小 MyThread(Semaphore sem, int count) {
this.sem = sem;
this.count = count;
} public void run() {
try {
// 从信号量中获取count个许可
sem.acquire(count); Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " acquire count="+count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放给定数目的许可,将其返回到信号量。
sem.release(count);
System.out.println(Thread.currentThread().getName() + " release " + count + "");
}
}
}

某一次运行后的结果如下:

pool-1-thread-1 acquire count=5
pool-1-thread-2 acquire count=4
pool-1-thread-1 release 5
pool-1-thread-2 release 4
pool-1-thread-3 acquire count=7
pool-1-thread-3 release 7

原文地址:http://www.2cto.com/kf/201402/278471.html

Java 7之多线程- Semaphore--转载的更多相关文章

  1. Java多线程学习(转载)

    Java多线程学习(转载) 时间:2015-03-14 13:53:14      阅读:137413      评论:4      收藏:3      [点我收藏+] 转载 :http://blog ...

  2. 使用总结:java多线程总结 <转载>

    转载 https://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html java多线程总结 2011-08-28 20:08  Ro ...

  3. Java NIO Channel之FileChannel [ 转载 ]

    Java NIO Channel之FileChannel [ 转载 ] @author zachary.guo 对于文件 I/O,最强大之处在于异步 I/O(asynchronous I/O),它允许 ...

  4. [Java][读书笔记]多线程编程

    前言:最近复习java,发现一本很好的资料,<J​a​v​a​2​参​考​大​全​ ​(​第​五​版​)​> ​ ​H​e​r​b​e​r​t​.Schildt.书比较老了,06年的,一些 ...

  5. Java并发:多线程和java.util.concurrent并发包总结

    多线程和java.util.concurrent并发包 转载:

  6. Java中的 多线程编程

    Java 中的多线程编程 一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序 ...

  7. java高级---->Thread之Semaphore的使用

    Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制.今天我们就学习一下Semaphore的用法. java中多线程Semaphore的使用 关于Semapho ...

  8. Java中的多线程技术全面详解

    本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上 ...

  9. Java基础技术多线程与并发面试【笔记】

    Java基础技术多线程与并发 什么是线程死锁? ​死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...

  10. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

随机推荐

  1. 数据库,inner join,left join right join 的区别

    假设有两个表: 学生和课程 student:              class: id    student          id       class    studentId 1      ...

  2. ECSHOP错误Redefining already defined constructor for class如何解决

    本地PHP环境PHP5.4,安装ecshop2.7.3后,很多地方会报如下的错 Redefining already defined constructor for class XXX 使用和类名相同 ...

  3. Ajax实现的长轮询不阻塞同一时间内页面的其他Ajax请求(同域请求)

    最近要做一个来电的弹屏功能,利用OM 系统的接口,OM系统发送请求到接口程序,分析数据添加到mysql数据库中,然后把最新的数据id 跟今日来电的总的数量存储到memcache 中.弹屏程序根据读取的 ...

  4. 七天学会SALTSTACK自动化运维 (2)

    七天学会SALTSTACK自动化运维 (2) 导读 Grains Pillar 总结 参考链接 导读 上一篇主要介绍了安装和基本的使用方法,但是我认为如果理解了相关概念的话,使用会更加顺手,因为毕竟每 ...

  5. springtest+juint开发测试如下:

    项目结构目录如下: UserMapper.java 为接口文件.User 为实体类.UserMapper.xml 为对应mybatis的xml文件.test为对应的测试包 applicationtes ...

  6. 1. android

    http://blog.csdn.net/mirkerson/article/details/7238815

  7. java和javascript获取word文档的书签位置对比

    1.javascript:把IE浏览器的activex都打开,使用如下网页,可以看到书签顺序和位置: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...

  8. 使用单调队列优化的 O(nm) 多重背包算法

    我搜索了一下,找到了一篇很好的博客,讲的挺详细:链接. 解析 多重背包的最原始的状态转移方程: 令 c[i] = min(num[i], j / v[i]) f[i][j] = max(f[i-1][ ...

  9. One git command may cause you hacked(CVE-2014-9390)

    0x00 背景 CVE-2014-9390是最近很火的一个漏洞,一个git命令就可能导致你被黑,我不打算深入探讨这个漏洞的细节,官方已经在https://github.com/blog/1938-gi ...

  10. oracle中的exists 和not exists 用法详解(转)

    有两个简单例子,以说明 “exists”和“in”的效率问题 1) select * from T1 where exists(select 1 from T2 where T1.a=T2.a) ; ...