Java并发包学习笔记

1、Semaphore与Exchanger的使用

    Semaphore该类的主要作用就是限制线程的并发数量,它所提供的功能是Synchronized关键字的升级版,可以让线程同步(一个一个执行任务,而不是并行执行)来保证程序逻辑的正确性。

 1.1、类Semaphore的常用API

  1. 构造函数的参数 int permits
    它代表着同一时间内,最多允许多少个线程同时执行acquire()与release()之间的代码,无参方法acquire()的作用是使用一个许可(permits),使用的是减法操作,release()是释放1个许可。permits的值默认是1。
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
注:

  • 当permits的值大于1时,该类并不能保证线程的安全性,可能出现脏数据。
  • acquire可有参数,acquire(int permits)表示每调用一次该方法,就使用x个许可,release类似,只是每次释放x个许可。
  • 如果多次调用release()方法可以动态增加permits的个数。
  1. acquireUniterrupitbly():它的作用是使等待进入acquire()方法的线程,不允许被中断。它也有重载方法,acquireUniterruptibly(int permits)表示每次获取x个许可。
  2. availablePermits()与drainPermits()方法:通常用于调试,availablePermits()返回当前可用的许可,而drainPermits()方法获取并返回立即可用的许可个数,并将许可置为0.
  3. 公平与非公平信号量:公平则获得许可的顺序与线程启动有关(不能说前面一定能获得,仅仅使概率的保证),非公平则与顺序无关。Semaphore的构造方法中传入true or false,true表示公平,false则相反。 公平锁效率没有非公平锁效率高,它可能会发生饿死,默认使用的是非公平锁。
    在这里插入图片描述
  4. tryAcquire()方法与tryAcquire(int permits, long timeout ,TimeUnit unit):作用是尝试获得1个许可,如果加入参数 permits表示尝试获得x个许可,如果获取不到则放回false,此方法通常与if语句结合使用,如果没有获取到,则走else语句,程序继续向下执行,没有阻塞。而tryAcquire(int permits, long timeout ,TimeUnit unit)表示在指定的时间内尝试获取x个许可。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

1.2、Semaphore的实验

1.2.1、多进路-多处理-多出路实验

本实验允许多个线程同时处理自己的任务。
例如以下案例,有5个线程,但只有三个线程可以同时处理自己的任务。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2.2 多进路-单处理-多出路

本实验是允许多个线程同时进入(处理任务),但是处理任务的顺序要是同步的,这就叫单处理。
其他类不变
在这里插入图片描述
在这里插入图片描述

1.2.3:使用Semaphore实现多生产者/多消费者模式

本实验同时有20个厨师(生产者)/(顾客)消费者线程,但同时只能有10个厨师进行生产(设置为20个的原因怕某个原因导致某个线程死亡,而使得总厨师线程或顾客线程达不到标准数量),12个顾客进行就餐,而且最多只有四个盘子可以存放菜品。
Service类

package com.zjw;


import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Service {
    volatile private Semaphore semaphore1=new Semaphore(10);
    volatile private Semaphore semaphore2=new Semaphore(12);
    volatile private ReentrantLock lock1=new ReentrantLock();
    volatile private Condition condition1= lock1.newCondition();
    volatile private Condition condition2= lock1.newCondition();
    volatile private ArrayList<String>list=new ArrayList<>();
    volatile private int i=0;
    public boolean isEmpty(){
        if(list.size()==0){
            return true;
        }else {
            return false;
        }
    }
    public boolean isFull(){
        if(list.size()==4){
            return true;
        }else {
            return false;
        }
    }
    public void service1(){
          try {
            semaphore1.acquire();
            lock1.lock();
            while(isFull()){
                condition1.await();
            }
            System.out.println("厨师"+Thread.currentThread().getName()+":"+i);
            list.add(Thread.currentThread().getName()+i++);
              condition2.signalAll();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }finally {
              lock1.unlock();
              semaphore1.release();
          }
      }
      public void service2(){
          try {
            semaphore2.acquire();
            lock1.lock();
            while(isEmpty()){
                condition2.await();
            }
            String string=list.remove(0);
            System.out.println("顾客"+Thread.currentThread().getName()+":"+string);
              condition1.signalAll();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }finally {
              lock1.unlock();
              semaphore2.release();
          }
      }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.3:类Exchanger的使用

exchange的功能是使两个线程之间传输数据,它比wait/notify要更加方便,它在传输数据类型上没有任何的限制。它具有阻塞的特色,即此方法调用后需要其他线程来去的数据才能继续进行下去。

1.3.1:使用exchange()传递数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2:CountDownLatch和CyclicBarrier的使用

这两个工具类可以使线程在同步处理上更加灵活,比如支持同步计数重置,等待同步线程个数等常见的功能。

2.1:CountDownLatch的使用

Latch的中文翻译是门阀,也就是有"门锁"的功能,所以当们没有打开的时候,N个人是不能进入屋内的。它相当与于在某处设置一个屏障,需等到所有人都到达后在放行,它使用await()方法判断count的计数是否为0,不为0的话则等待,其它线程可以使用countDown()方法时计数1,当计数减为0时,呈等待的线程继续运行。

2.1.1:裁判等全部运动员准备后开启比赛

本实验是想要实现裁判等到所有运动员全部准备完毕后,开始比赛的效果。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
注:该实验并不完善,裁判(Main)判断线程是否全部到达只是使用了sleep(2000)的语句,即使某个线程在2秒内没有到达,但是比赛还是会开始。

2.1.2: 完善的比赛流程

该实验需要使用多个CountDownLatch()来实现业务要求。
Thread类

package com.zjw;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;

public class Thread_a extends Thread{
   private CountDownLatch comingTag;//裁判等待所有运动员到达
   private CountDownLatch waitTag;//所有运动员等待裁判说准备开始,并不是立即开始
   private CountDownLatch waitRunTag;//等待起跑
   private CountDownLatch beginTag;//起跑
   private CountDownLatch endTag;//所有运动员结束比赛

    public Thread_a(CountDownLatch comingTag, CountDownLatch waitTag,
                    CountDownLatch waitRunTag, CountDownLatch beginTag, CountDownLatch endTag) {
        this.comingTag = comingTag;
        this.waitTag = waitTag;
        this.waitRunTag = waitRunTag;
        this.beginTag = beginTag;
        this.endTag = endTag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep((long) (Math.random()*1000));
            System.out.println(Thread.currentThread().getName()+"到达了起点");
            comingTag.countDown();
            System.out.println(Thread.currentThread().getName()+"等待裁判说准备");
            waitTag.await();
            System.out.println(Thread.currentThread().getName()+"准备起跑姿势");
            Thread.sleep((long) (Math.random()*1000));
            waitRunTag.countDown();
            System.out.println(Thread.currentThread().getName()+"等待比赛开始");
            beginTag.await();
            System.out.println(Thread.currentThread().getName()+"开始起跑");
            Thread.sleep((long) (Math.random()*1000));
            System.out.println(Thread.currentThread().getName()+"到达终点");
            endTag.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
结果:

裁判员等待所有运动员到达
运动员2到达了起点
运动员2等待裁判说准备
运动员6到达了起点
运动员6等待裁判说准备
运动员0到达了起点
运动员0等待裁判说准备
运动员8到达了起点
运动员8等待裁判说准备
运动员7到达了起点
运动员7等待裁判说准备
运动员9到达了起点
运动员9等待裁判说准备
运动员4到达了起点
运动员4等待裁判说准备
运动员5到达了起点
运动员5等待裁判说准备
运动员3到达了起点
运动员3等待裁判说准备
运动员1到达了起点
运动员1等待裁判说准备
裁判员说准备开始
各就各位
运动员6准备起跑姿势
运动员4准备起跑姿势
运动员3准备起跑姿势
运动员0准备起跑姿势
运动员2准备起跑姿势
运动员1准备起跑姿势
运动员9准备起跑姿势
运动员5准备起跑姿势
运动员7准备起跑姿势
运动员8准备起跑姿势
运动员5等待比赛开始
运动员9等待比赛开始
运动员7等待比赛开始
运动员6等待比赛开始
运动员0等待比赛开始
运动员1等待比赛开始
运动员8等待比赛开始
运动员4等待比赛开始
运动员2等待比赛开始
运动员3等待比赛开始
比赛开始
运动员5开始起跑
运动员4开始起跑
运动员3开始起跑
运动员8开始起跑
运动员1开始起跑
运动员0开始起跑
运动员6开始起跑
运动员2开始起跑
运动员7开始起跑
运动员9开始起跑
运动员2到达终点
运动员3到达终点
运动员4到达终点
运动员7到达终点
运动员8到达终点
运动员0到达终点
运动员5到达终点
运动员1到达终点
运动员6到达终点
运动员9到达终点
比赛结束

2.1.3: 一些其他API

1、await(long timeout,TimeUnit unit):使线程在指定的最大时间单位内进入等待(Waiting)状态,如果超过这个时间则自动唤醒,继续执行下去。
2、getCount():获取当前计数的值。

2.2:CyclicBarrier的概念

类CyclicBarrier和Semaphore及CountDownLatch一样,都是同步辅助类,根据字面Cyclic的意思,则说明它的屏障或说门阀,可以重新使用。它的功能大致与CountBarrierLatch一样,让多个线程在某处等待别的线程,但是它与CountBarrierLatch还是有一些区别的:

  1. CountBarrierLatch的作用:一个线程或多个线程,等待另外一个线程或多个线程完成某件事情才能继续执行。
  2. CyclicBarrier的作用:是多个线程之间互相等待,任何一个线程完成之前,其他线程都必须等待。

CyclicBarrier的类比:比赛过程中,必须等到所有人都到达起点,比赛才开始,都到达终点比赛才结束。

2.2.1:CyclicBarrier的使用

这个实验是实现每从筹齐两个线程到达屏障处,就可以继续进行下去。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注:这个实验还说明屏障的重值性(到达了指定的parties后重置为0),而且方法getNumberWaiting()方法可以返回当前已经有几个线程到达了屏障点。

2.2.2:其他API的使用

1、await(long timeout,TimeUnit unit):指定时间到达parties数量的线程后,则继续执行下去,否则报错。
2、getNumberWaiting():返回有几个线程到达屏障点。
3、 getParties():返回parties的个数。
4、构造函数可以传个实现Runnable的类,在到达parties数量后执行该类的run方法。
5、isBroken():

  • CyclicBarrier初始化时,broken=false,表示屏障未破损。
  • 如果正在等待的线程被中断,则broken=true,表示屏障破损。
  • 如果正在等待的线程超时,则broken=true,表示屏障破损。
  • 如果有线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障回到未破损状态。

6、 reset():使得CyclicBarrier回归初始状态,直观来看它做了两件事:
如果有正在等待的线程,则会抛出BrokenBarrierException异常,且这些线程停止等待,继续执行。将是否破损标志位broken置为false。

3:类Phaser(建议使用)

虽然类CyclicBarrier已经解决了类CountDownLatch的种种缺点,但是不可否认,类CyclicBarrier本身还是存在一些问题的,比如不可以动态增加parties的计数,调用一次await()仅仅占用一个计数,所以在JDK1.7中有了类phaser来解决这样的问题,它执行的是加法操作。而且它可以设置多处屏障,类似与跑步的多个赛段一样。
Phaser的类比:比如跑步有多道比赛,运动员每到达一道赛段就必须等到所有运动员都到达此赛段比赛才可以开始。

3.1:一些常用API

它的主要方法有这些:
在这里插入图片描述

1、arrive():使parties的值加1,但并不会在屏障处等待。
2、arriveAndAwaitAdvance():与CyclicBarrier的await方法一样,该运动员到达第一赛段终点,等到所有运动员都到达该终点处开始第二赛段的比赛。
3、arriveAndDeregister():使当前的运动员(线程)退出比赛,且parties值减1。
4、getPhaser()与onAdvance(int phase, int registeredParties ):getPhaser()获取当前已经到达了第几个屏障,onAdvance()需要自己重写,可以在构造函数的后面直接重写,类似与CyclicBarrier传入的Runnable参数一样,在指定parties到达屏障处自动调用,且他有boolean类型的返回值,如果是true,则不等待,Phaser呈无效/摧毁状态,如果是false则,Phaser继续工作。
5、getRegister()与register():获取注册的parties数量,后面的是是parties的值加1。
6、bulkRegister(int Parties):批量增加parties的值。
7、getArrivedParties()与getUnarrivedParties():返回已经使用[例如调用arriveAndAwaitAdvance()]和未使用的partise的数量。
8、awaitAdvance(int phase):如果传入参数的值与当前的getPhase方法的返回值相同,则在屏障处等待,否则继续像下面执行。
9、awaitAdvanceInterruptibly(int phaser,long timeout TimeUnit unit):在指定栏数等待最大的单位时间,如果没有发生改变则报错,否则继续执行。
10、forceTermination()和isTerminated():前者使phaser屏障功能失效,后者是判断它是否失效。

3.2:Phaser的使用

案例一:arriveAndAwaitAdvance()、onAdvance()方法实践
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注:根据结果我们可以看出调用onAdvance方法的线程是第一个到达屏障处的线程。

案例二:arriveAndDeregister()方法、awaitAdvanceInterruptibly(int phaser,long timeout TimeUnit unit)
在案例一的基础上添加一个类thread_c
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例三:awaitAdvance()
Test类不变
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤独的偷学者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值