资讯专栏INFORMATION COLUMN

(八)java多线程之Semaphore

DesGemini / 1953人阅读

摘要:在每个线程获取之前,必须先从信号量获取许可。注意,因为同时可能发生取消,所以返回并不保证有其他线程等待获取许可。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。

本人邮箱:
欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代码已经全部托管github有需要的同学自行下载

引言

这节课,我们就开始讲一下信号量Semaphore

理论

Semaphore:一个可计数的信号量。一般,一个semaphore 信号量是一组许可证。如果必要,那个每次acquire获取许可都是阻塞的,直接一个许可证是可用的,并获取到。每次release释放,都会增加一个许可证,潜在的,也会释放一个阻塞请求。然而。并非每次许可对象都可以被使用的,这个Semaphore信号量只保存几个可用的许可证和相应的操作。

如果有几个线程数要访问几个共享资源的话,那么这时候就应该使用信号量。举例说明:这个有类Pool类,它就使用信号量在控制多线程去访问那么几个有限items

class Pool {
   private static final int MAX_AVAILABLE = 100;
   private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

   public Object getItem() throws InterruptedException {
     available.acquire();
     return getNextAvailableItem();
   }

   public void putItem(Object x) {
     if (markAsUnused(x))
       available.release();
   }

   // Not a particularly efficient data structure; just for demo

   protected Object[] items = ... whatever kinds of items being managed
   protected boolean[] used = new boolean[MAX_AVAILABLE];

   protected synchronized Object getNextAvailableItem() {
     for (int i = 0; i < MAX_AVAILABLE; ++i) {
       if (!used[i]) {
          used[i] = true;
          return items[i];
       }
     }
     return null; // not reached
   }

   protected synchronized boolean markAsUnused(Object item) {
     for (int i = 0; i < MAX_AVAILABLE; ++i) {
       if (item == items[i]) {
          if (used[i]) {
            used[i] = false;
            return true;
          } else
            return false;
       }
     }
     return false;
   }
}

在每个线程获取item之前,必须先从信号量获取许可。保证这个item对用户来说是可以使用的。当线程结束使用item时,并让item返回item池,这信号量会释放这个许可,之后允许使用线程可以获取到这个item。必须要注意的,在程序中,在获取许可和释放许可的死胡同并没有使用同步锁,信号量封装了限制对池的访问所需的同步,与维护池本身的一致性所需的任何同步。

Semaphore(int permits): 创建一个指定数量的许可的信号量

Semaphore(int permits, boolean fair) 创建一个指定数量的许可,并保证每个线程都是公平的,当fairtrue时,信号量会安装先进先出的原则来获取许可.

acquire() 在当前信号量中获取一个许可.当前线程会一直阻塞直到有一个可用的许可,或被其他线程中断.

acquireUninterruptibly(): 在当前信号量中获取一个许可.当前线程会一直阻塞直到有一个可用的许可.

tryAcquire() 在当前信号量尝试获取一个许可,如果有可用,则获取到这个许可,并立即返回true,后缀立即返回false

tryAcquire 在当前信号量获取一个许可,当前线程会一直阻塞直到有一个可用的许可.或指定时间超时了,或被其他线程中断.

release() 释放一个许可,把让它返回到这个信号量中.

acquire(int permits) 请求指定数量的许可,如果有足够的许可可用,那么当前线程会立刻返回,如果许可不足,则当前会一直等待,直到被其他线程中断,或获取到足够的许可.

acquireUninterruptibly(int permits) 请求指定数量的许可,如果有足够的许可可用,那么当前线程会立刻返回,如果许可不足,则当前会一直等待,直到获取到足够的许可.

tryAcquire(int permits) 在当前信号量尝试获取指定数量的许可,如果有可用,则获取到这个许可,并立即返回true,后缀立即返回false

tryAcquire(int permits, long timeout, TimeUnit unit) 在指定的超时时间,当前信号量尝试获取指定数量的许可,如果有可用,则获取到这个许可,并立即返回true,后缀立即返回false

release(int permits) 释放指定数量的许可

availablePermits() 返回当前信号量还有几个可用的许可

drainPermits() 请求并立即返回当前信号量可用的全部许可

reducePermits(int reduction) 根据指定的缩减量减小可用许可的数目。此方法在使用信号量来跟踪那些变为不可用资源的子类中很有用。此方法不同于 acquire,在许可变为可用的过程中,它不会阻塞等待。

isFair() 返回当前的信号量时候是公平的

hasQueuedThreads() 查询是否有线程正在等待获取。注意,因为同时可能发生取消,所以返回 true 并不保证有其他线程等待获取许可。此方法主要用于监视系统状态。

getQueueLength() 返回正在等待获取的线程的估计数目。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。此方法用于监视系统状态,不用于同步控制。

getQueuedThreads() 返回一个 collection,包含可能等待获取的线程。因为在构造此结果的同时实际的线程 set 可能动态地变化,所以返回的 collection 仅是尽力的估计值。所返回 collection 中的元素没有特定的顺序。此方法用于加快子类的构造速度,提供更多的监视设施。

例子

看了前面那么方法的介绍,恐怕你想吐的的心都有了吧?还是让我们回归轻松愉快的例子来吧.这里我们还是继续举小明小红谈人生和理想的例子.之前他们在卧室里谈了好几百毫秒的人生和理想.顿时都感觉身疲惫,感觉身体好像被掏空了一样.所以这里他们都想洗一个热水澡,但是沐浴室只有三间,那就抢吧..ok,开始编程...

首先,先编写一个沐浴室ShowerRoom

public class ShowerRoom {
    private static final int MAX_SIZE = 3;
    Semaphore semaphore = new Semaphore(MAX_SIZE);

    public void bathe(String name){
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " 洗唰唰啊..洗唰唰... ");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName() + " 终于洗完澡了...");
            semaphore.release();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

然后编写让小明小红去洗澡操作 BoyAndGril

public class BoyAndGril implements Runnable{
    ShowerRoom showerRoom;
    public BoyAndGril(ShowerRoom showerRoom) {
        this.showerRoom = showerRoom;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        showerRoom.bathe(name);
    }
}

最后,测试一下

public class TestMain {

    public static void main(String[] args) {
        Set boyAndGril = new HashSet<>();
        ShowerRoom showerRoom = new ShowerRoom();
        for (int i = 0; i < 10; i ++){
            boyAndGril.add(new Thread(new BoyAndGril(showerRoom), "小明" + i + "号"));
        }
        for (int i = 0; i < 10; i ++){
            boyAndGril.add(new Thread(new BoyAndGril(showerRoom), "小红" + i + "号"));
        }
        for (Thread thread : boyAndGril){
            thread.start();
        }
    }
}

运行一下结果

小红3号 洗唰唰啊..洗唰唰... 
小红6号 洗唰唰啊..洗唰唰... 
小明0号 洗唰唰啊..洗唰唰... 
小红3号 终于洗完澡了...
小红2号 洗唰唰啊..洗唰唰... 
小红6号 终于洗完澡了...
小明2号 洗唰唰啊..洗唰唰... 
小明0号 终于洗完澡了...
小红1号 洗唰唰啊..洗唰唰... 
小红2号 终于洗完澡了...
小明5号 洗唰唰啊..洗唰唰... 
小明2号 终于洗完澡了...
小明7号 洗唰唰啊..洗唰唰... 
小红1号 终于洗完澡了...
小红0号 洗唰唰啊..洗唰唰... 
小明5号 终于洗完澡了...
小明7号 终于洗完澡了...
小明4号 洗唰唰啊..洗唰唰... 
小红4号 洗唰唰啊..洗唰唰... 
小红0号 终于洗完澡了...
小明3号 洗唰唰啊..洗唰唰... 
小明4号 终于洗完澡了...
小明9号 洗唰唰啊..洗唰唰... 
小红4号 终于洗完澡了...
小红7号 洗唰唰啊..洗唰唰... 
小明3号 终于洗完澡了...
小红5号 洗唰唰啊..洗唰唰... 
小红5号 终于洗完澡了...
小红9号 洗唰唰啊..洗唰唰... 
小红7号 终于洗完澡了...
小明6号 洗唰唰啊..洗唰唰... 
小明9号 终于洗完澡了...
小明1号 洗唰唰啊..洗唰唰... 
小红9号 终于洗完澡了...
小红8号 洗唰唰啊..洗唰唰... 
小明1号 终于洗完澡了...
小明8号 洗唰唰啊..洗唰唰... 
小明6号 终于洗完澡了...
小红8号 终于洗完澡了...
小明8号 终于洗完澡了...

ok,运行正常,程序中不会发生四个人以及四个以上的人在同时洗澡的情况.

如果有人觉得这个好像也没有使用什么共享资源啊,没有上面那个例子的item pool,那行,那把有关semaphore的代码注释掉,再运行一下.

小红3号 洗唰唰啊..洗唰唰... 
小红6号 洗唰唰啊..洗唰唰... 
小明0号 洗唰唰啊..洗唰唰... 
小红2号 洗唰唰啊..洗唰唰... 
小明2号 洗唰唰啊..洗唰唰... 
小红1号 洗唰唰啊..洗唰唰... 
小明5号 洗唰唰啊..洗唰唰... 
小明7号 洗唰唰啊..洗唰唰... 
小红0号 洗唰唰啊..洗唰唰... 
小明4号 洗唰唰啊..洗唰唰... 
小红4号 洗唰唰啊..洗唰唰... 
小明3号 洗唰唰啊..洗唰唰... 
小明9号 洗唰唰啊..洗唰唰... 
小红7号 洗唰唰啊..洗唰唰... 
小红5号 洗唰唰啊..洗唰唰... 
小红9号 洗唰唰啊..洗唰唰... 
小明6号 洗唰唰啊..洗唰唰... 
小明1号 洗唰唰啊..洗唰唰... 
小红8号 洗唰唰啊..洗唰唰... 
小明8号 洗唰唰啊..洗唰唰... 
小红3号 终于洗完澡了...
小红2号 终于洗完澡了...
小明2号 终于洗完澡了...
小红6号 终于洗完澡了...
小明0号 终于洗完澡了...
小明5号 终于洗完澡了...
小红0号 终于洗完澡了...
小明7号 终于洗完澡了...
小红1号 终于洗完澡了...
小明4号 终于洗完澡了...
小红4号 终于洗完澡了...
小明3号 终于洗完澡了...
小明9号 终于洗完澡了...
小红8号 终于洗完澡了...
小红5号 终于洗完澡了...
小红9号 终于洗完澡了...
小明6号 终于洗完澡了...
小明1号 终于洗完澡了...
小红7号 终于洗完澡了...
小明8号 终于洗完澡了...

发现这几十个人都同时在三间沐浴室里洗澡,那么肯定有只是一间会出现两人或两人以上同时洗澡的情况.如果浴室够大,大家都没有意见,那还好.就是如果肥皂掉了,这个时候,小明就得考虑要不要弯腰去捡了....

打赏

如果觉得我的文章写的还过得去的话,有钱就捧个钱场,没钱给我捧个人场(帮我点赞或推荐一下)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69898.html

相关文章

  • Java线程工具箱Semaphore

    摘要:多线程工具箱之前言这一篇谈一下信号量。信息信息信息信息信息信息信息信息信息信息信息小结适用于多线程请求数量资源的场景,但无法解决单多个线程对同一资源访问的竞争性访问。在后面我们在我们的多线程工具箱里面陆续会提到。 Java多线程工具箱之Semaphore 前言 这一篇谈一下Semaphore:信号量。 将Semaphore类比为为信号灯,被继承Runable的线程类比为列车:理解信号量...

    FleyX 评论0 收藏0
  • Java线程进阶(二十)—— J.U.Csynchronizer框架:Semaphore

    摘要:当线程使用完共享资源后,可以归还许可,以供其它需要的线程使用。所以,并不会阻塞调用线程。立即减少指定数目的可用许可数。方法用于将可用许可数清零,并返回清零前的许可数六的类接口声明类声明构造器接口声明 showImg(https://segmentfault.com/img/bVbfdnC?w=1920&h=1200); 本文首发于一世流云的专栏:https://segmentfault...

    boredream 评论0 收藏0
  • Java线程&高并发

    摘要:线程启动规则对象的方法先行发生于此线程的每一个动作。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。通过获取到数据,放入当前线程处理完之后将当前线程中的信息移除。主线程必须在启动其他线程后立即调用方法。 一、线程安全性 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行...

    SQC 评论0 收藏0
  • Java线程编程同步器

    摘要:倒计时锁,线程中调用使进程进入阻塞状态,当达成指定次数后通过继续执行每个线程中剩余的内容。实现分阶段的的功能测试代码拿客网站群三产创建于年月日。 同步器 为每种特定的同步问题提供了解决方案 Semaphore Semaphore【信号标;旗语】,通过计数器控制对共享资源的访问。 测试类: package concurrent; import concurrent.th...

    liangdas 评论0 收藏0
  • 后台开发常问面试题集锦(问题搬运工,附链接)

    摘要:基础问题的的性能及原理之区别详解备忘笔记深入理解流水线抽象关键字修饰符知识点总结必看篇中的关键字解析回调机制解读抽象类与三大特征时间和时间戳的相互转换为什么要使用内部类对象锁和类锁的区别,,优缺点及比较提高篇八详解内部类单例模式和 Java基础问题 String的+的性能及原理 java之yield(),sleep(),wait()区别详解-备忘笔记 深入理解Java Stream流水...

    spacewander 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<