并发工具类(三)控制并发线程的数量 Semphore

前言
  JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch、CyclicBarrier、Semphore、Exchanger、Phaser;
  CountDownLatch、CyclicBarrier、Semphore、Phaser 这四个工具类提供一种并发流程的控制手段;而Exchanger工具类则提供了在线程之间交换数据的一种手段。

简介

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
很多年以来,我都觉得从字面上很难理解Semaphore所表达的含义,只能把它比作是控制流量的红绿灯,比如XX马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入XX马路,但是如果前一百辆中有五辆车已经离开了XX马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
应用场景
  Semaphore可以用于做

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

private static final int THREAD_COUNT = 30;

private static ExecutorService threadPool = Executors
        .newFixedThreadPool(THREAD_COUNT);

private static Semaphore s = 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 {
                    s.acquire();
                    System.out.println("save data");
                    s.release();
                } catch (InterruptedException e) {
                }
            }
        });
    }

    threadPool.shutdown();
}

}
在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证。还可以用tryAcquire()方法尝试获取许可证。

Semphore的方法摘要
1、获取许可
  API中提供了多种的方式获取锁:

可以获取一个、多个许可;
提供阻塞、非阻塞、超时的方式获取许可;
除了可中断、还提供一个非中断的方式获取锁;
public void acquire() throws InterruptedException
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被

中断。

public void acquire(int permits) throws InterruptedException

获取多个许可。
从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被

中断。

public void acquireUninterruptibly()
从此信号量中获取许可,在有可用的许可前将其阻塞。

不可中断。

public void acquireUninterruptibly(int permits)

获取多个许可。
从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

不可中断。

public boolean tryAcquire()
仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

非阻塞的方式尝试获取许可。

public boolean tryAcquire(int permits)
仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

超时等待获取许可

public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException
如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。

2、许可的释放
public void release( ):
释放一个许可,将其返回给信号量。
public void release(int permits)
释放给定数目的许可,将其返回到信号量。

3、提供的监控方法
public int availablePermits( )
返回此信号量中当前可用的许可数
public int drainPermits()
获取并返回立即可用的所有许可
public final int getQueueLength()
返回正在等待获取的线程的估计数目。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。此方法用于监视系统状态,不用于同步控制。
public final boolean hasQueuedThreads()
查询是否有线程正在等待获取。
public boolean isFair()
如果此信号量的公平设置为 true,则返回 true。

protected 方法:
protected Collection
返回一个 collection,包含可能等待获取的线程。因为在构造此结果的同时实际的线程 set 可能动态地变化,所以返回的 collection 仅是尽力的估计值。所返回 collection 中的元素没有特定的顺序。
protected void reducePermits(int reduction)
根据指定的缩减量减小可用许可的数目。此方法在使用信号量来跟踪那些变为不可用资源的子类中很有用

@ Example 获取、释放多个许可
try {
Semaphore semaphore = new Semaphore(5);
//获取一个许可
semaphore.acquire();
//一次性获取4个许可
semaphore.acquire(4);
System.out.println("Semaphore 剩下的许可数量:"+semaphore.availablePermits());
//一次性释放5个许可
semaphore.release(5);
System.out.println("Semaphore 剩下的许可数量:"+semaphore.availablePermits());
//再释放5个许可
semaphore.release();
semaphore.release();
semaphore.release(3);
System.out.println("Semaphore 剩下的许可数量:"+semaphore.availablePermits());

} catch (InterruptedException e) {
e.printStackTrace();

运行结果:

Semaphore 剩下的许可数量:0
Semaphore 剩下的许可数量:5
Semaphore 剩下的许可数量:10
从上面的运行结果可以看出,

构造方法的 new Semaphore(5)中参数5并不是最终的许可数量,可以通过release()方法增加许可数量。
本人测试用例

package com.wxx.demo;

import com.wxx.demo.util.IdUtiles;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

@RunWith(SpringRunner.class)
//@SpringBootTest(classes = LeisureWebApplication.class)
public class TaskTest {

@Test
public void taskTest(){

    Runnable task = new Runnable() {

        int count = 0;

        @Override
        public void run() {

            count ++;
            try{
                String id = IdUtiles.creatId();
                System.out.println(id);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(count);
            System.out.println("Thread : " + Thread.currentThread().getId());

            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

        }
    };

    double executeTime = this.executeTime(100, task);
    System.out.println("执行时间: " + executeTime);
}

private double executeTime(int taskCount,Runnable task){

    CountDownLatch start = new CountDownLatch(1);
    CountDownLatch end = new CountDownLatch(taskCount);

    for (int i = 0; i < taskCount ; i++) {
        Thread thread = new Thread() {

            public void run(){
                try {
                    start.await();

                    try {
                        task.run();
                    }finally {
                        end.countDown();
                    }

                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        };

        thread.start();
    }

    long startTime = System.nanoTime();

    //开启开关
    start.countDown();

    long endTime = System.nanoTime();

    return endTime - startTime;
}

}
package com.wxx.demo.util;

import java.text.SimpleDateFormat;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**

  • @Author : leisure

  • @Date : 2019/1/17
    */
    public class IdUtiles {

    private static String lead = "leisure";
    private static int Guid = 100;
    private static Semaphore semaphore = new Semaphore(5,false);
    /**

    • 创建以字符串打头结尾自增的唯一id
    • @return
      */
      public static synchronized String creatId() throws InterruptedException{
      //测试控制方法内的并发线程数 测试放开synchronized
      //semaphore.acquire();
      semaphore.tryAcquire(1,1000, TimeUnit.MILLISECONDS);
      int i = semaphore.availablePermits();
      System.out.println("当前可用许可" + i);
    int i1 = semaphore.drainPermits();
    System.out.println("当前立即可用的许可" + i1);

    boolean b = semaphore.hasQueuedThreads();
    System.out.println("当前是否有线程等待" + b);

    boolean fair = semaphore.isFair();
    System.out.println("当前信号是否公平" + fair);
    long l = System.currentTimeMillis();

    int queueLength = semaphore.getQueueLength();
    System.out.println("等待线程数" + queueLength);
    Thread.sleep(100);
    Guid += 1;

    String format = new SimpleDateFormat("yyyy").format(l);

    if (Guid > 999){
        Guid = 100;
    }

    String id = lead + format + l + Guid;

    semaphore.release();
    return id;
}

}
注:文章源地址:https://www.cnblogs.com/jinggod/p/8494246.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,548评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,497评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,990评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,618评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,618评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,246评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,819评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,725评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,268评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,356评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,488评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,181评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,862评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,331评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,445评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,897评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,500评论 2 359

推荐阅读更多精彩内容