关于后端:吃透Java-并发何须惧工具来相助

46次阅读

共计 5113 个字符,预计需要花费 13 分钟才能阅读完成。

大家好,我是小菜。
一个心愿可能成为 吹着牛 X 谈架构 的男人!如果你也想成为我想成为的人,不然点个关注做个伴,让小菜不再孤独!

本文次要介绍 搬砖必备的并发工具类

来都来了,点个在看怎么了~!

微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

作为一名躺平的搬砖工程师,在内卷期间,从容不迫地搬砖可能曾经离你而去。砖是一种共享资源,现如今每个搬砖工都想谋求品质的又要同时放弃高效的搬砖速率,在抢夺的状况下会不会呈现并发的状况?你搬过的砖却计算在他人的 KPI 上,本来只想 躺平 ,却没想到 躺平 也要蒙受如此不公!本来只需煎一面的咸鱼,当初还得把另一面翻过来再煎~!

终于,躺平的搬砖工决定不再躺平,他捏紧了拳头,牙齿咬得“格格”作响,他的脸像蜡一样的黄,嘴唇咬得发白,本来不多的头发一颤一颤地,全身都在瑟瑟地发抖,狠狠的下定了决定:我肯定要解决并发问题!让搬砖行业失常的运行~!

什么?失常运行,那就得解决并发问题!

好了好了,氛围对头了,这个时候小菜缓步退场,那么就进入主题,解决并发问题你罕用的并发工具类有哪些?

JDK 的并发包中曾经提供了几个十分有用的并发工具类。

  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • Exchanger

这几个可能有些小伙伴看的眼生,可能有点生分,看的眼生却不会用和看的生分的也并无区别。那接下来咱们通过简略的论述,就能让你在平时的开发中运用自如!

一、CountDownLatch

这是个在平时开发中呈现频率较高的并发工具,它是一个 倒计数器。是一个十分实用的多线程管制工具类,这个工具类经常用来控制线程期待,能够让一个线程期待直到计数器完结再开始执行!

咱们不用一开始就深究源码,先会用再善用。因而咱们简略看个繁难的例子

《一个都不能少》

小王老师是一个严格的老师,她上课有些许任性,必须等到所有学生(10 名)都到场后才会开始上课,也就是凡是一个学生不在场,都不会开课。

咱们要遵循 一个都不能少 的要求,也就是当 学生人数 < 总人数 的时候不能执行上课的这个动作。那么这个时候咱们应该怎么解决这个问题呢?

咱们课前点名,减少一个 if 判断,当人数不满足的状况下,就不会进入到 上课 的动作中。这个可能是一个惯性思维,大部分同学都会这样操作。那么问题来了,有些学生可能只是因为早退,错过了点名的判断,当 if 执行完结后就不会再判断,那么错过就是错过,只管后续人数曾经到齐了,但最终是开不了课的!

想想再改良下,如果因为 if 只判断一次而造成的问题,那咱们能不能始终判断,那就能够用到了while 或者 for 始终循环判断。解决思路是正确的,那咱们就顺藤引出 CountDownLatch 的用法

代码不长,但不晓得后果是否如咱们所愿:

咱们能够看到,当 10 名学生都达到后,小王老师开始上课了,但如果咱们这是一个学生没达到呢?

当达到人数未合乎预期,则不能失常上课,目前看曾经满足咱们的需要了。那咱们紧接着模仿一下学生早退的场景~

仍然是学号为 10 的同学,虽迟但到,课还是能够失常进行上的!

看来 CountDownLatch 真是一个好工具,简简单单就帮咱们解决了该问题!那他怎么解决的呢?

CountDownLatch 是通过一个计数器来实现的,首先设置一个计数器的初始值。每当实现一个工作后,计数器的值就会减 1,当计数器达到0 时,它示意所有工作都曾经实现,而后在闭锁上期待的线程能够复原执行工作.

咱们首先能够要看的是 CountDownLatch 的构造方法

public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

该办法须要初始化一个计数值,并初始化一个 Sync,咱们这个时候无妨大胆猜想,CountDownLatch底层便是靠 Sync 实现的!咱们来看看 Sync 是个啥玩意?

能够看到在 Sync 外部保护着一个平安变量 state,它的值便是 计数器的值。其中有两个重要办法:tryAcquireShared(int acquires)tryReleaseShared(int releases)。那这两个办法有什么用呢?

咱们能够先回到 CountDownLatch 类中,下面咱们曾经看到该类构造函数的作用,接下来须要意识其中两个重要的办法:countDown()await()。在咱们看来,countDown() 办法便是用来将计数值减 1await() 办法是用来阻塞判断计数值是否为 0?那咱们进入对应办法看是如何实现的

public void countDown() {sync.releaseShared(1);
}
---
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}

这两个办法调用的都是 AQS 中的两个办法:(我这边间接贴源码正文,认真看哦~!)

countDown()

await()

下面便是 CountDownLatch 的实现,那咱们无妨想想该工具类在实时零碎中的应用场景:

  1. 实现最大的并行性

当咱们想要同时启动多个线程,实现最大水平的并行性。例如,咱们想测试一个单例类,如果咱们创立一个初始值为 1 的 CountDownLatch,并让所有线程都在这个锁上期待,那么咱们就能够很轻松的实现测试,只须要调用一次 countDown()办法就能够让所有期待线程同时复原执行

  1. 开始执行前期待 n 个线程实现各自的工作

当咱们应用程序执行前,确保某些前置动作须要执行

  1. 死锁检测

咱们能够应用 n 个线程访问共享资源,在每次测试阶段的线程数目是不同的,这样能够尝试产生死锁

二、CyclicBarrier

CyclicBarrier是另外一种多线程并发管制工具。Cyclic 意为循环,也就是说这个计数器能够 重复应用 ,它比CountDownLatch 更加弱小一点,它要做的事件是,让一组线程达到一个屏障(也能够叫同步点)时被阻塞,直到最初一个线程达到屏障时,屏障才会开门,所有被屏障拦挡的线程才会持续工作。

也就是说 CyclicBarrier 是加法计时器,咱们一样通过以上 《一个都不能少》 例子来示例如何应用

这里就不再演示缺课与早退的示范,与上述 CountDownLatch 实现形式统一

这里咱们仍然关注两个办法,一个是 构造方法,一个是 await()

咱们仍然进入到 CyclicBarrier 类中查看构造方法

public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

能够发现和下面说到的 CountDownLatch 还是有出入的,该构造方法只是做了 屏障点 的记录,咱们重点还是要看 await() 办法

public int await() throws InterruptedException, BrokenBarrierException {
    try {return dowait(false, 0L);
    } catch (TimeoutException toe) {throw new Error(toe); // cannot happen
    }
}

追根朔底咱们得看 dowait() 办法,进入办法能够发现实现形式并不简单。因为代码有点长,咱们截取重点阐明

CountDownLatch 不同的是,屏障点变量并没有应用 volatile 润饰,那么就得毋庸就得加锁使之线程平安!

以上便是 CyclicBarrier 的整个实现过程,具体咱就不抠细节了~!

CyclicBarrierCountDownLatch 还是有点相似的,然而咱们要分明他们之间的区别:

  1. CountDownLatch: 一个线程(或多个),期待另外 N 个线程实现某件事情之后才会执行
  2. CyclicBarrier: N 个线程之间互相期待,任何一个线程实现之前,所有的线程都必须期待

比拟重要的一点:CountDownLatch 不可反复利用,CyclicBarrier 不可反复利用

三、Semaphore

信号量(Semaphore)是为多线程提供了更为弱小的管制办法。从狭义上来讲,信号量是对锁的扩大。无论是外部锁 synchronized 还是重入锁ReentrantLock,一次都只容许一个线程拜访一个资源,而信号量却能够指定多个线程,同时拜访某一个共享资源。

咱们简略看个繁难的例子

《抢车位》

本来一个小区有 5 个地上停车位曾经能够很好的满足业主的停车需要,然而这两年车辆数暴增,简直家家一车,车位天然供不应求,那只能遵循先到先得的准则!

而后咱们看下执行后果:

能够看到 5 个车位是共享资源,只有先到的业主能力抢到车位,当抢到车位的业主来到后,后续的业主能力进入获取到车位!

咱们提取出关注点 构造方法acquire()release()

构造方法

public Semaphore(int permits) {sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

是的,Semaphore 有两个构造方法,区别在于是否应用 偏心锁。而后咱们持续看 aquire()release()

public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}

public void release() {sync.releaseShared(1);
}

excuse me~? 后面有认真看的小伙伴,必定感觉眼生了,这调用的办法岂不是和下面 CountDownLatch 的一样?是的,这两个并发工具类,底层都是 调用 AQS 的线程办法。如果不晓得这两个办法作用的同学,能够上翻查看,这里不再赘述!

依据这个工具类联合上述例子,咱们能够在 流量管制 的时候应用!特地是公共资源无限的利用场景,比方数据库连贯,如果有一个需要要读取几万个文件的数据,因为都是 IO 密集型的工作,咱们能够启动几十个线程去并发地读取,然而咱们得通过 硬盘 -> 内存 -> 数据库,而如果数据库的连接数只有 10 个,那咱们这个时候就必须要管制只有 10 个线程能够同时获取数据库连贯保留数据,这个时候就能够应用 Semaphore 来做流量管制~!

四、Exchanger

看到这个名称,不晓得有多少小伙伴脑子里想的是 这是啥?。瞎话说,这个工具类出镜率真不高,用的比拟少。Exchanger 是一个用于线程间合作的工具类。它可用于线程间的数据交换,它提供了一个同步点,两个线程能够替换彼此的数据,。这两个线程通过 Exchanger 办法替换数据,如果第一个线程先执行 exchange() 办法,它会始终期待第二个线程也执行 exchanger() 办法,当两个线程都达到同步点时,这两个线程就能够替换数据,将本线程生产进去的数据传递给对方。

这里留神的是 两个线程,不存在“ 三角关系 ”

在没有通过 exchange() 时,数字线程 打印的应该是数字, 字母线程 打印的应该是字母,然而通过了 exchange()后果就产生了逆转:

留神: 如果两个线程中有一个没有执行 exchange() 办法,那么则会始终期待

为了防止这种状况的产生,咱们能够在 exchange()中加上超时工夫!

那么这个工具类有什么利用场景呢?咱们想想如果在一个线程的执行工作中创立某个对象的生产代价很高,而另外一个线程工作也须要生产到这个对象,那咱们就能够借助 Exchanger 来帮忙咱们传输类对象。甚至于能够实现 生产者 - 消费者 模式!

以上便是几种并发工具类的应用与利用场景,当然下面提到的利用场景只是一小部分,更多的当然须要在开发中持续开掘,做到 会用且善用

看到最初,搬砖工程师掐灭了手中的烟头,烟雾弥漫的空气中传来一句经久不灭的话语:他娘的,没想到这年头搬个砖都不容易了

不要空谈,不要贪懒,和小菜一起做个 吹着牛 X 做架构 的程序猿吧~ 点个关注做个伴,让小菜不再孤独。咱们下文见!

明天的你多致力一点,今天的你就能少说一句求人的话!
我是小菜,一个和你一起变强的男人。 💋
微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

正文完
 0