参考博文 :

  1. 线程同步工具(一)
  2. 线程同步工具(二)控制并发访问多个资源
  3. 并发工具类(三)控制并发线程数的Semaphore

使用Semaphore模拟互斥锁

当一个线程想要访问某个共享资源,首先,它必须获得semaphore。如果semaphore的内部计数器的值大于0,那么semaphore减少计数器的值并允许访问共享的资源。计数器的值大于0表示,有可以自由使用的资源,所以线程可访问并使用它们。

另一种情况,如果semaphore的计数器的值等于0,那么semaphore让线程进入休眠状态一直到计数器大于0。计数器的值等于0表示全部的共享资源都正被线程们使用,所以线程想要访问就必须等到某个资源成为自由的。

当线程使用完共享资源时,他就必须释放出semaphore,增加semaphore的内部计数器的值,让其他线程可以访问共享资源。

  • 方法:

    设置Semaphore的permits大小为1,这样同一个时刻,只能有一个线程,进入acquire和release保护的方法体内。
  • Code:

import java.util.concurrent.Semaphore; //1. 使用semaphore保护的互斥打印队列
class PrintQueue { private final Semaphore semaphore; public PrintQueue() {
// 只设定一个许可,这样同一个时刻 只能一个线程执行 printJob方法,从而实现互斥锁
semaphore = new Semaphore(1);
} //2.实现Implement the printJob()方法,此方法可以模拟打印文档,并接收document对象作为参数。
public void printJob(Object document) { try {
semaphore.acquire(); //3.然后,实现能随机等待一段时间的模拟打印文档的行。
long duration = (long) (Math.random() * 10);
System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n", Thread.currentThread().getName(), duration);
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//7.最后,释放semaphore通过调用semaphore的release()方法。
semaphore.release();
System.out.printf("%s: PrintQueue: Printing a Job release the permits \n", Thread.currentThread().getName());
}
}
} // 模拟打印进程
class PrintThread extends Thread { PrintQueue printQueue ;
public PrintThread (PrintQueue printQueue) {
this.printQueue = printQueue ;
} @Override
public void run() {
printQueue.printJob(new Object());
}
} public class PrintQueueTest {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue() ; PrintThread a = new PrintThread(printQueue) ;
a.setName("A"); PrintThread b = new PrintThread(printQueue);
b.setName("B"); PrintThread c = new PrintThread(printQueue) ;
c.setName("C"); a.start();
b.start();
c.start();
}
}

控制并发访问多个资源

在上面的例子中,我们使用了semaphore来保护访问一个共享资源的,或者说一个代码片段每次只能被一个线程执行。但是semaphore也可以用来保护多个资源的副本,也就是说当你有一个代码片段每次可以被指定数量的多个线程执行时,可以考虑使用Semaphore。

下面的例子会有一个print queue 但可以在3个不同的打印机上打印文件。


import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; class PrintQueue2 {
// 模拟打印机 数组
private boolean freePrinters[];
private Lock lockPrinters; private final Semaphore semaphore; public PrintQueue2() {
semaphore = new Semaphore(2);
//模拟 初始化三个可用的打印机
freePrinters = new boolean[]{true,true,true};
lockPrinters = new ReentrantLock();
} public void printJob(Object document) {
int assignedPrinter = -1 ;
try {
semaphore.acquire(); // 获取可用的打印机 序号
assignedPrinter = getPrinter(); //7.然后, 随机等待一段时间来实现模拟打印文档的行。
long duration = (long) (Math.random() * 10);
System.out.printf("%s: PrintQueue: Printing a Job in Printer%d during %d seconds\n", Thread.currentThread().getName(), assignedPrinter, duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(assignedPrinter != -1) { // 模拟释放打印机
freePrinters[assignedPrinter] = true;
}
semaphore.release();
System.out.printf("%s: PrintQueue: 打印结束释放打印机\n", Thread.currentThread().getName());
}
} // 遍历找到当前可用的 打印机的索引下标
private int getPrinter() {
int ret = -1;
try {
lockPrinters.lock();
for (int i = 0; i < freePrinters.length; i++) {
if (freePrinters[i]) {
ret = i;
freePrinters[i] = false;
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lockPrinters.unlock();
}
return ret;
}
} // 模拟打印进程
class PrintThread extends Thread { PrintQueue2 printQueue2 ;
public PrintThread (PrintQueue2 printQueue) {
this.printQueue2 = printQueue ;
} @Override
public void run() {
printQueue2.printJob(new Object());
}
} class PrintQueueTest {
public static void main(String[] args) {
PrintQueue2 printQueue = new PrintQueue2() ; PrintThread a = new PrintThread(printQueue) ;
a.setName("A"); PrintThread b = new PrintThread(printQueue);
b.setName("B"); PrintThread c = new PrintThread(printQueue) ;
c.setName("C"); a.start();
b.start();
c.start();
}
}

使用Semaphore控制并发线程数

Semaphore可以用来做流量控制,特别公用资源有限的应用场景,比如数据库连接。假设有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要进行存储到数据库中,而数据库的连接数只有10几个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控。

public class SemaphoreTest2 {

    private static final int THREAD_COUNT = 30;

    private static ExecutorService threadPool = Executors
.newFixedThreadPool(THREAD_COUNT); private static Semaphore semaphore = new Semaphore(10); public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("save data");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
});
} threadPool.shutdown();
}
}

在代码中,虽然有30个线程正在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits)接收一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。

线程同步工具 Semaphore类使用案例的更多相关文章

  1. 线程同步工具 Semaphore类的基础使用

    推荐好文: 线程同步工具(一) 线程同步工具(二)控制并发访问多个资源 并发工具类(三)控制并发线程数的Semaphore 简介 Semaphore是基于计数的信号量,可以用来控制同时访问特定资源的线 ...

  2. 【java并发】线程同步工具Semaphore的使用

    Semaphore通常用于限制可以访问某些资源(物理或逻辑的)的线程数目,我们可以自己设定最大访问量.它有两个很常用的方法是acquire()和release(),分别是获得许可和释放许可.  官方J ...

  3. Java核心知识点学习----线程同步工具类,CyclicBarrier学习

    线程同步工具类,CyclicBarrier日常开发较少涉及,这里只举一个例子,以做备注.N个人一块出去玩,相约去两个地方,CyclicBarrier的主要作用是等待所有人都汇合了,才往下一站出发. 1 ...

  4. 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  5. 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...

  6. 多线程面试题系列(8):经典线程同步 信号量Semaphore

    前面介绍了关键段CS.事件Event.互斥量Mutex在经典线程同步问题中的使用.本篇介绍用信号量Semaphore来解决这个问题. 首先也来看看如何使用信号量,信号量Semaphore常用有三个函数 ...

  7. 转---秒杀多线程第八篇 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  8. linux系统编程:线程同步-信号量(semaphore)

    线程同步-信号量(semaphore) 生产者与消费者问题再思考 在实际生活中,仅仅要有商品.消费者就能够消费,这没问题. 但生产者的生产并非无限的.比如,仓库是有限的,原材料是有限的,生产指标受消费 ...

  9. 多线程状态与优先级、线程同步与Monitor类、死锁

    一.线程状态 二.线程优先级 三.初步尝试多线程 class Program { static void Main(string[] args) { while (true) { MessagePri ...

随机推荐

  1. Consul 入门

    1. 什么是Consul? Consul 有很多组件,对于整体来说,它是一个服务发现和服务配置的工具,它提供了一下特性: 服务发现 健康检查 KV存储 多数据中心 2.安装Consul 以下是在 Ce ...

  2. UVA129 暴力dfs,有许多值得学习的代码

    紫书195 题目大意:给一个困难的串,困难的串的定义就是里面没有重复的串. 思路:不需要重新对之前的串进行判重,只需要对当前的加入的字符进行改变即可. 因为是判断字典序第k个的字符串,所以要多一个全局 ...

  3. [洛谷P1858] 多人背包

    洛谷题目链接:多人背包 题目描述 求01背包前k优解的价值和 输入输出格式 输入格式: 第一行三个数K.V.N 接下来每行两个数,表示体积和价值 输出格式: 前k优解的价值和 输入输出样例 输入样例# ...

  4. Independence.

    It's not giving up, it's letting go, and moving to a better place. I will survive and be the one who ...

  5. photoshop的魔棒工具怎么用来抠图

    魔棒工具是photoshop中提供的一种可以快速形成选区的工具,对于颜色边界分界明显的图片,能够一键形成选区,方便快捷. 本教程通过一个简单的实例,教新手怎么用Photoshop魔棒工具快速形成抠图选 ...

  6. Codeforces Round #483 (Div. 2) [Thanks, Botan Investments and Victor Shaburov!]

    题目链接:http://codeforces.com/contest/984 A. Game time limit per test:2 seconds memory limit per test:5 ...

  7. bzoj 1406 数论

    首先问题的意思就是在找出n以内的所有x^2%n=1的数,那么我们可以得到(x+1)(x-1)=y*n,那么我们知道n|(x+1)(x-1),我们设n=a*b,那么我们对于任意的a,我们满足n%a==0 ...

  8. bzoj 2733 平衡树启发式合并

    首先对于一个连通块中,询问我们可以直接用平衡树来求出排名,那么我们可以用并查集来维护各个块中的连通情况,对于合并两个平衡树,我们可以暴力的将size小的平衡树中的所有节点删掉,然后加入大的平衡树中,因 ...

  9. Java中的return语句使用总结

    Java中的return语句总是和方法有密切关系,return语句总是用在方法中,有两个作用,一个是返回方法指定类型的值(这个值总是确定的),一个是结束方法的执行(仅仅一个return语句).   在 ...

  10. Linux目录结构与文件权限——(五)

    1.目录结构