java semaphore 等待_深入学习理解(7):java:高效的解决死锁问题的线程通讯方式:Semaphore 和 BlockingQueue...

经典原始问题:生产者和消费者的问题,其实在实际项目中很容易遇到这样的无奈的问题,但是面对这样的问题的时候我们首先想到的就是多线程批处理,通过notify()…的处理,但只这样的处理只能给我们贴上对java多线程不熟悉的标签。比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型。下面我们就来看看吧!

Semaphore

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。

Semaphore分为单值和多值两种,前者只能被一个线程获得,后者可以被若干个线程获得。

以一个停车场运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。

在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。

更进一步,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。 当一个线程调用Wait(等待)操作时,它要么通过然后将信号量减一,要么一直等下去,直到信号量大于一或超时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为加操作实际上是释放了由信号量守护的资源。

在java中,还可以设置该信号量是否采用公平模式,如果以公平方式执行,则线程将会按到达的顺序(FIFO)执行,如果是非公平,则可以后请求的有可能排在队列的头部。

JDK中定义如下:

Semaphore(int permits, boolean fair)

//创建具有给定的许可数和给定的公平设置的Semaphore。

Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java并发库Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合

package concurrent;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.Executor;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

Semaphore semaphore=new Semaphore(1);

Listlist=new ArrayList<>();

public static void main(String[] args) {

SemaphoreDemo s=new SemaphoreDemo();

//创建线程管理

ExecutorService executorService=Executors.newCachedThreadPool();

executorService.execute(s.new ThreadTestA());//启动线程

executorService.execute(s.new ThreadTestB());//

executorService.shutdown();//结束服务

}

private class ThreadTestA implements Runnable{

@Override

public void run() {

try {

System.out.println("这个是线程A获取许可证之前");

semaphore.acquire();//获得许可证

System.out.println("这个是线程A获取许可证之后");

list.add("test");

Thread.sleep(1000);//模拟延迟

} catch (InterruptedException e) {

e.printStackTrace();

}

finally{

semaphore.release();//吊销

System.out.println("这个是线程A释放许可证之后");

}

}

}

private class ThreadTestB implements Runnable{

@Override

public void run() {

try {

System.out.println("这个是线程B获取许可证之前");

semaphore.acquire();

System.out.println("这个是线程B获取许可证之后");

if(list.size()!=0)

System.out.println(list.get(0));

} catch (InterruptedException e) {

e.printStackTrace();

}

finally{

semaphore.release();

System.out.println("这个是线程B释放许可证之后");

}

}

}

}

运行结果可能会出现几种情况

1

这个是线程A获取许可证之前

这个是线程A获取许可证之后

这个是线程B获取许可证之前

这个是线程A释放许可证之后

这个是线程B获取许可证之后

test

这个是线程B释放许可证之后

2

这个是线程A获取许可证之前

这个是线程A获取许可证之后

这个是线程B获取许可证之前

这个是线程B获取许可证之后

test

这个是线程B释放许可证之后

这个是线程A释放许可证之后

3

这个是线程B获取许可证之前

这个是线程B获取许可证之后

这个是线程B释放许可证之后

这个是线程A获取许可证之前

这个是线程A获取许可证之后

这个是线程A释放许可证之后

BlockingQueue

首先是一个接口。有很多子接口和实现类:

BlockingQueue也是java.util.concurrent下的主要用来控制线程同步的工具。

BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类

1、ArrayBlockingQueue:一个由数组支持的有界阻塞队列,规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。

2、LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的。

3、PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序。

4、SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。

LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。

下面我们通过一个例子来说明:

Bread

package concurrent;

public class Bread {

private String name;

private String color;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getColor() {

return color;

}

public void setColor(String color) {

this.color = color;

}

}

生产者机器 BreadMachine

package concurrent;

import java.awt.Color;

import java.util.Random;

import java.util.concurrent.BlockingQueue;

public class BreadMachine implements Runnable{

private BlockingQueue queue;

public BreadMachine(BlockingQueue queue){

this.queue=queue;

}

@Override

public void run() {

System.out.println("开始生产面包");

Bread bread=new Bread();

bread.setColor(Color.BLACK+"");

Random random=new Random();

bread.setName(random.nextInt(100)+"面包");

try {

queue.put(bread);

System.out.println(bread.getName()+"已经生产完毕!去吃吧");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

消费者People

package concurrent;

import java.util.concurrent.BlockingQueue;

public class People implements Runnable{

BlockingQueue bread;

public People(BlockingQueue bread ) {

this.bread=bread;

}

@Override

public void run() {

try {

System.out.println("我需要面包");

System.out.println("这个二货吃了"+bread.take().getName());

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

测试程序

package concurrent;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

public class BlockingQueenDemo {

static BlockingQueue blockingQueue=new ArrayBlockingQueue<>(5);

public static void main(String[] args) {

BreadMachine breadMachine=new BreadMachine(blockingQueue);

People people=new People(blockingQueue);

for(int i=0;i<5;i++){

new Thread(people, "people").start();

new Thread(breadMachine, "breadMachine").start();

}

}

}

运行效果;

开始生产面包

开始生产面包

我需要面包

我需要面包

开始生产面包

我需要面包

开始生产面包

我需要面包

我需要面包

开始生产面包

29面包已经生产完毕!去吃吧

23面包已经生产完毕!去吃吧

这个二货吃了29面包

66面包已经生产完毕!去吃吧

31面包已经生产完毕!去吃吧

36面包已经生产完毕!去吃吧

这个二货吃了23面包

这个二货吃了66面包

这个二货吃了31面包

这个二货吃了36面包

由于队列的大小限定成了5,所以最多只有两个产品被加入到队列当中,而且消费者取到产品的顺序也是按照生产的先后顺序,原因就是LinkedBlockingQueue和ArrayBlockingQueue都是按照FIFO的顺序存取元素的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值