JUC 是学习 Java 并发编程的小伙伴不可避免的一个 pkg,JUC提供了对并发编程的底层支持,比如我们熟悉的线程池、MQ、线程同步... 都有JUC的影子,下面我们一起来看看JUC下比较重要的几个class。

CountdownLatch

先看一下 **latch **是什么意思:



门闩大家都知道吧,假设我们现在有一扇铁门,每个线程跑到这铁门时都取下一把门闩,当所有的门闩都从门上取下来时,门就打开了,线程就可以继续往下执行。

public class CountdownLatchTest {
public static void main(String[] args) throws InterruptedException {
// 传入放多少个门闩
CountDownLatch cdl = new CountDownLatch(10); // 启 10 个线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 取下一把门闩");
// 每个线程运行完就取下一把门闩
cdl.countDown();
}, "线程" + i).start();
}
// 铁门在这等着门闩被取完了才打开
cdl.await(); System.out.println("门打开了");
}
}

运行结果:



在实际开发中,我们常用多个门闩来对多个线程的执行顺序进行控制,后面有机会给大家分享一下案例。

CyclicBarrier

我们要搞清楚一个东西肯定得知道他是什么意思,大佬对命名都是很讲究的。(我大学刚接触编程都是 a,b,c,d,e,f,g命名)



单词每个都认识,连在一起就不知道是什么意思了。

循环的障碍?循环的屏障?其实都无所谓,自己挑一个容易理解的名字即可,我理解的是“循环的栅栏”

什么是栅栏?

就是拦在你前面的一个障碍物,这个障碍物你一个人推不开,得多叫几个兄弟一起来推才行,推开了才能继续往前走。

public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(10, () -> {
// todo:人齐了该干嘛
System.out.println("人齐了,开冲");
}); for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "已就位");
try {
// 线程都在这里等着,直到人气才会执行 CyclicBarrier 里面的方法
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, i + " 号兄弟").start();
}
}
}

运行结果:



CountDownLatch 不同的是,CyclicBarrier 还有个 Cyclic ,我相信你已经猜到了,我们启动 20 个线程看看



门闩是一次性的,当门闩全部取下来这个门就打开了,而栅栏是有弹性的,当人满了把它推开后,它又弹回来把路又堵住了,又需要 n 个人才能把栅栏推开。

如果有小伙伴感兴趣可以去看下它是怎么实现循环的

Semaphore

Semaphore 的意思是:信号量,信号量在线程同步中也是很常见的,你可以把它看作是一个 计数器 cnt

  • 当 cnt > 0 说明还有资源
  • 当 cnt < 0 说明没有资源了

举个栗子:比如办公室有一台打印机,但是今天领导让大家写一份加薪报告,大家都忙着用这台打印机打印自己的报告,准备今晚加餐吃鸡腿。

public class SemaphoreTest {
public static void main(String[] args) {
// 只有一台打印机
Semaphore s = new Semaphore(1); for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
// 去抢打印机(不一定抢得到)
s.acquire();
System.out.println(Thread.currentThread().getName() + " 拿到打印机了,今晚可以加鸡腿了");
// 把打印机还回去
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "员工 " + i).start();
}
} }

输出结果:



**Semaphore **适用于资源有限,消费者无限的场景,比如打印机就是一个很好的例子。

因为我初始化的资源是 1 ,所以会让你觉得和 差不多,其实不然

资源还可以初始化为 10 20 50 100.... 你可能想到了什么,没错,就是限流!

有大量的请求进来,我们只允许我们指定的数量请求进行处理,更多的用途就等以后你来开发了

ReadWriteLock

读写锁

读锁:也就是共享锁,这把锁允许多个线程持有

写锁:也就是独占锁,这把锁只允许一个线程拥有

说人话的意思就是:读不加锁,写加锁

我们都知道加锁的效率会比较低,如果我们只是对资源进行读取,其实是没有线程安全的问题,这时候再加锁就显得有点脱了裤子放屁(多此一举)

所以我们就有了读写锁:读不加锁,写加锁

public class ReadWriteLockTest {
private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 读写锁通过 ReentrantReadWriteLock 实例进行创建,说明读写锁也是可重入的
private static final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private static final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); public static void main(String[] args) {
ReadWriteLockTest o = new ReadWriteLockTest(); // 先把 写方法注释掉,模拟读操作
for (int i = 0; i < 10; i++) {
new Thread(() -> {
read();
//write();
}, "线程" + i).start();
} } public static void read() {
// 上读锁
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 开始读取数据");
System.out.println(Thread.currentThread().getName() + " 读取数据成功");
} finally {
readLock.unlock();
} } public static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入数据");
System.out.println(Thread.currentThread().getName() + "写入数据成功");
} finally {
writeLock.unlock();
}
}
}

读操作结果:



我们可以看到执行顺序,线程 8 还没读取成功,线程 9 又在读取数据了。

写操作结果:



我们可以看到,虽然线程不是顺序执行的,但是 新的线程 一定是 等上一个线程写入成功后,自己才开始写如数据

也就是说,开始写入和写入成功永远是成对出现。

BlockingQueue

阻塞队列:相信大家对 Java 中的容器一定不陌生,容器又分了两类:Collection && Map

Collection 中的 List && Set ** **大家都已经耳熟能详了,但是 Queue 可能很少用到

但是我可以告诉你,Queue 的重要程度丝毫不亚于你所知道另外几种

BlockingQueue 更是 线程池MQ 发展的基石

复习一下 Queue,Queue 是一个队列:从尾进,从头出(先进先出)

Queue 给我们提供了几个API:

  • add() 添加一个元素,队列满则报错
  • offer() 添加一个元素,队列满不报错
  • poll() 移除队头元素
  • peek() 获取队头元素

而BlockingQueue 给我们提供了几个阻塞的API:

  • put() 往队列里面放元素,如果队列满,就一直等着(阻塞),直到队列空出来位置。
  • take() 往队列里面取元素,如果队列为空,就一直等着(阻塞),直到队列有元素加进来

还记得线程池核心参数中的任务队列吗?就是让我们传入一个 BlockingQueue

BlockingQueue 分为有界和无界两种:

  • 有界: ArrayBlockingQueue (数组实现,要指定数组长度,当然有界啦)
  • 无界:LinkedBlockingQueue(链表实现,新任务直接挂在尾节点上,当然就无界了)

说了这么多,我们浅写一个 生产者/消费者 模型:

public class BlockingQueueTest {
public static void main(String[] args) {
// 实例化一个容量为 5 的 BlockingQueue
ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<>(5); new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
abq.put(i);
System.out.println("生产者生产数据:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Integer take = abq.take();
System.out.println("消费者消费数据:" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); }
}

运行结果:



这就是一个简单的 MQ

SynchronousQueue

SynchronousQueue 是一个很特殊的 BlockingQueue,特殊在:



它是没有容量,也就是说:生产者把生产出来的数据,手把手递给消费者才算完成

conclusion

其实还有 DelayQueue(延时队列),TransferQueue(控制生产者生产速率用) ... 没有讲,今天我累了 TAT,下节准备分享一下 线程池相关的知识,铁铁们,点个赞再走吧。

Java并发编程(一)JUC同步类的更多相关文章

  1. 【Java并发编程二】同步容器和并发容器

    一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并 ...

  2. java并发编程:线程安全管理类--原子操作类--AtomicInteger

    在java并发编程中,会出现++,--等操作,但是这些不是原子性操作,这在线程安全上面就会出现相应的问题.因此java提供了相应类的原子性操作类. 1.AtomicInteger

  3. Java并发编程笔记之Unsafe类和LockSupport类源码分析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar ...

  4. java并发编程:线程同步和锁

    一.锁的原理 java中每个对象都有一个内置锁.当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this)有关的锁.获得一个对象的锁也称为获取锁,当程序运 ...

  5. java并发编程基础——线程同步

    线程同步 一.线程安全问题 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安 ...

  6. Java并发编程阅读笔记-同步容器、工具类整理

  7. Java并发编程(3) JUC中的锁

    一 前言 前面已经说到JUC中的锁主要是基于AQS实现,而AQS(AQS的内部结构 .AQS的设计与实现)在前面已经简单介绍过了.今天记录下JUC包下的锁是怎么基于AQS上实现的 二 同步锁 同步锁不 ...

  8. Java并发编程,互斥同步和线程之间的协作

    互斥同步和线程之间的协作 互斥同步 Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLo ...

  9. java并发编程:线程安全管理类--原子包--java.util.concurrent.atomic

    java.util.concurrent.atomic 的描述 AtomicBoolean 可以用原子方式更新的 boolean 值. AtomicInteger 可以用原子方式更新的 int 值. ...

  10. java并发编程:线程安全管理类--原子操作类--AtomicBoolean

    1.类AtomicBoolean

随机推荐

  1. 4天带你上手HarmonyOS ArkUI开发——《HarmonyOS ArkUI入门训练营之健康生活实战》

     <HarmonyOS ArkUI入门训练营之健康饮食应用>是面向入门开发者打造的实战课程系列.特邀华为终端BG高级开发工程师作为本次训练营讲师,以健康饮食为例,开展技术教学及实战案例分享 ...

  2. CentOS升级内核-- CentOS9 Stream/CentOS8 Stream/CentOS7

    官方文档在此 升级原因 当我们安装一些软件(对,我说的就是Kubernetes),可能需要新内核的支持,而CentOS又比较保守,不太升级,所以需要我们手工升级. # 看下目前是什么版本内核 unam ...

  3. 了解redis的单线程模型工作原理?一篇文章就够了

    1.首先redis是单线程的,为什么redis会是单线程的呢?从redis的性能上进行考虑,单线程避免了上下文频繁切换问题,效率高:从redis的内部结构设计原理进行考虑,redis是基于Reacto ...

  4. 使用python获取知乎**话题下的所有回答,并统计后发布。

    第一步:获取话题需要的url需要,并向上取整 for idx in range(0,math.ceil(totals/5)): url = f"https://www.zhihu.com/a ...

  5. 写给大家看的“不负责任” K8s 入门文档

    前言 2019 年下半年,我做了一次转岗,开始接触到 Kubernetes,虽然对 K8s 的认识还非常的不全面,但是非常想分享一下自己的一些收获,希望通过本文能够帮助大家对 K8s 有一个入门的了解 ...

  6. 终于要跟大家见面了,Flink 面试指南

    面试,一个令人大多数同学头疼的问题,要么成功进入心仪公司,要么沮丧与其失之交臂.但是,如果能在面试前就能知道面试官将会问的问题,然后可以好好提前准备,这种感觉是不是特别棒? 之前社区帮大家汇总了目前 ...

  7. 5月25日,阿里云开源 PolarDB-X 将迎来重磅升级发布

    ​简介:2022年5月25日,阿里云开源 PolarDB-X 将升级发布新版本!PolarDB-X 从 2009 年开始服务于阿里巴巴电商核心系统, 2015 年开始对外提供商业化服务,并于 2021 ...

  8. 一看即会:Serverless 应用开发的 7 个实战小技巧

    ​简介:干货满满,马住收藏! ​ Serverless 应用开发的 7 个经验心得 作者说:Serverless 架构下的应用开发,与传统架构的应用开发还是有比较大的区别点的,例如天然分布式架构会让很 ...

  9. 基于DataWorks搭建新零售数据中台

    文章作者:许日(欢伯),在2016年盒马早期的时候,转到盒马事业部作为在线数据平台的研发负责人,现任阿里云计算平台DataWorks建模引擎团队负责人. 文章简介:本篇文章向大家分享新零售企业如何基于 ...

  10. 如何基于MaxCompute快速打通数据仓库和数据湖的湖仓一体实践

    简介: MaxCompute 是面向分析的企业级 SaaS 模式云数据仓库,以 Serverless 架构提供快速.全托管的在线数据仓库服务,消除了传统数据平台在资源扩展性和弹性方面的限制,最小化用户 ...