JavaEE:多线程进阶(JUC [java.util.concurrent] 的常见类)

embedded/2024/10/10 17:16:17/

文章目录

  • JUC
    • 什么是JUC
    • Callable 接口
      • 理解 Callable
      • 理解FutureTask
    • ReentrantLock
    • 信号量 Semaphore
    • CountDownLatch


JUC

什么是JUC

JUC的全称为: java.util.concurrent.
JUC是Java并发工具包的一部分。它提供了一组并发编程工具和类,用于处理多线程编程和并发任务。

Callable 接口

这个接口非常类似于Runnable接口.
关于Runnable我们都知道,通过Runnable可以表示一个具体的任务,我们通过Runnable的run方法来描述接下来的代码要做什么事情.
Callable也是类似的效果,但是与Runnable相比,Callable提供了call方法,与Runnable的run方法相比,call方法带有返回值,而run方法是void.
因此,如果你是期望创建线程,让这个线程来返回一个结果,那么使用Callable要比Runnable要更方便一些.

比如说创建一个线程,让这个线程计算 1+2+3+…+1000.
使用Runnable的代码:

java">package javaEE.thread;public class B {private static int result;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {int sum = 0;for (int i = 0; i < 1000; i++) {sum += i;}result = sum;});t.start();t.join();System.out.println(result);}
}

使用Callable的代码:

java">package javaEE.thread;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class C {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() {int sum = 0;for (int i = 0; i < 1000; i++) {sum += i;}return sum;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();// FutureTask的作用:// 后续我们需要使用FutureTask来拿到最后的结果.// futureTask.get()就能拿到call方法的返回值// futureTask.get()带有阻塞功能,当线程t还没执行完,get就会阻塞// 直到t执行完毕,get才能返回.get就相当于"带有返回结果"的join.System.out.println(futureTask.get());}
}

注意:Thread不能传入Callable作为参数~
需要使用FutureTask传入Thread.

理解 Callable

Callable和Runnable相对,都是描述一个"任务",Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务.
Callable通常需要搭配FutureTask来使用,FutureTask用来保存Callable的返回结果,因为Callable往往是在另一个线程中执行的,啥时候执行完并不确定.
FutureTask就可以负责这个等待结果出来的工作.

理解FutureTask

想象去吃麻辣烫,当餐点好后,后厨就开始做了,同时前台会给你一张"小票",这个小票就是FutureTask,后面我们可以随时凭这张小票来查看自己的这份麻辣烫做好了没.

ReentrantLock

ReentrantLock为可重入互斥锁.
ReentrantLock是一种经典风格的锁,它提供lock和unlock方法来完成加锁和解锁.

java">package javaEE.thread;import java.util.concurrent.locks.ReentrantLock;public class D {private static int count = 0;public static void main(String[] args) throws InterruptedException {ReentrantLock locker = new ReentrantLock();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {locker.lock();count++;locker.unlock();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {locker.lock();count++;locker.unlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

使用ReentrantLock时要保证unlock能被执行到~

我们在开发过程中大部分情况下都是使用synchronized就可以了,那为啥还要搞一个ReentrantLock呢?
ReentrantLock相比synchronized还是有一些区别差异的.

  1. synchronized属于是关键字(底层是通过JVM的C++代码实现的).
    ReentrantLock则是标准库提供的类,它是通过Java代码来实现的.

  2. synchronized通过代码块控制加锁解锁,ReentrantLock通过调用lock/unlock方法来完成.unlock可能会遗漏,所以一般要把unlock放到finally中.

  3. ReentrantLock提供了tryLock这样的加锁风格,前面介绍的加锁,都是发现锁被别人占用了,就阻塞等待.
    tryLock在加锁失败的时候,不会阻塞,而是直接返回,通过返回值来反馈是加锁成功还是失败.(相当于给了程序员更多的可操作空间)

  4. ReentrantLock还提供了公平锁的实现.默认为非公平锁,我们可以在构造方法中传入参数,把它设置成公平锁.
    在这里插入图片描述

  5. ReentrantLock还提供了功能更强的"等待通知机制".
    synchronized是通过Object的wait/notify来实现等待-唤醒,每次唤醒的是一个随机等待的线程.
    ReentrantLock搭配Condition类实现等待-唤醒,可以更精确的控制唤醒某个指定的线程.

ReentrantLock和synchronized分别在什么场景下使用?

  • 锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更方便.
  • 锁竞争激烈的时候,使用ReentrantLock,搭配tryLock更灵活控制加锁的行为,而不是死等.
  • 如果需要使用公平锁,使用ReentrantLock.

信号量 Semaphore

信号量,用来表示"可用资源的个数",本质上是一个计数器.
系统申请资源时计数器+1(也称为"P"操作),释放资源时计数器-1(也称为"V"操作).
如果计数器为0了,系统还尝试申请资源,此时就会出现阻塞,直到有其他线程释放资源.

Semaphore的PV操作中的加减计数器操作都是原子的,可以在多线程环境下直接使用.

操作系统本身提供了信号量实现,JVM把操作系统的信号量封装了一下~

java">package javaEE.thread;import java.util.concurrent.Semaphore;public class E {public static void main(String[] args) throws InterruptedException {// 括号里写的就是可用资源个数,计数器的初始值.Semaphore semaphore = new Semaphore(3);semaphore.acquire(); // 申请资源System.out.println("申请资源");semaphore.acquire(); // 申请资源System.out.println("申请资源");semaphore.acquire(); // 申请资源System.out.println("申请资源");semaphore.release(); // 释放资源System.out.println("释放资源");semaphore.acquire(); // 申请资源System.out.println("申请资源");}
}

在这里插入图片描述
我们也可以通过Semaphore来实现类似加锁的效果.

java">package javaEE.thread;import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;public class D {private static int count = 0;public static void main(String[] args) throws InterruptedException {
//        ReentrantLock locker = new ReentrantLock(true);// 设置可用资源数为1Semaphore semaphore = new Semaphore(1);Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {
//                locker.lock();try {semaphore.acquire(); // 申请一个资源count++;semaphore.release(); // 释放一个资源} catch (InterruptedException e) {throw new RuntimeException(e);}
//                locker.unlock();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {
//                locker.lock();try {semaphore.acquire(); // 申请一个资源count++;semaphore.release(); // 释放一个资源} catch (InterruptedException e) {throw new RuntimeException(e);}
//                locker.unlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

运行结果为:100000.也是正确的~

CountDownLatch

同时等待N个任务结束.

很多时候,我们需要把一个大的任务,拆成多个小任务,使用多线程/线程池执行.
如何衡量,所有的任务都执行完毕了?
此时我们就可以使用CountDownLatch来达成这个效果.

java">package javaEE.thread;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class F {public static void main(String[] args) throws InterruptedException {// 通过线程池创建2个线程ExecutorService executorService = Executors.newFixedThreadPool(2);// 构造方法的数字,就是拆分出来的任务个数CountDownLatch countDownLatch = new CountDownLatch(4);for (int i = 0; i < 4; i++) {int id = i;executorService.submit(() -> {System.out.println("任务" + id + "开始执行");try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("任务" + id + "结束执行");// 完毕 over!!countDownLatch.countDown();});}// 当countDownLatch收到了4个"完成",此时所有的任务就完成了.// await => all wait// await 这个词也是计算机术语,它在Python/js 中的意思是async wait(异步等待).在这里不是这个意思哦~countDownLatch.await();System.out.println("所有任务执行完毕");}
}

在这里插入图片描述

本文到这里就结束了~


http://www.ppmy.cn/embedded/110373.html

相关文章

Python 数学建模——独立性检验

文章目录 前言卡方独立性检验原理代码实例 Fisher 精确检验 前言 独立性检验是概率论中比较重要的内容&#xff0c;常常用于检验两个变量是否具有关联关系。下面结合代码说说数学建模中如何使用 Python 进行独立性检验。 卡方独立性检验 原理 χ 2 \chi^2 χ2 &#xff08;卡…

《数字信号处理》学习06-因果系统与稳定系统

目录 一&#xff0c;因果系统 二&#xff0c;稳定系统 之前学习了系统中的线性时不变系统&#xff08; 系统&#xff09;&#xff0c;接下来学习线性时不变系统&#xff08; 系统&#xff09;中的因果系统与稳定系统。&#xff08;非LTI系统这里暂时不作为学习的要求&#xf…

JS_事件的简介和常见事件的绑定_01

HTML事件可以是浏览器行为&#xff0c;也可以是用户行为。当这些行为发生时&#xff0c;可以自动触发对应的JS函数的运行&#xff0c;我们称之为事件发生&#xff0c;JS的事件驱动指的就是行为触发代码运行的这种特点 一、事件的绑定方式 通过元素的属性绑定 on***通过DOM编程…

【秋招笔试题】机器人的数量

内存限制: 65536KB 题目描述: 今天&#xff0c;我们要进入一个充满机器人的神奇科技世界。这是一张由小红科技总部最新研发的网络格地图。每个格子都绘制着标志——它们标示着机器人的行进方向&#xff1b;这些标志会让机器人进入它们指向的下一个相邻格子。 网格有 n 行 m 列…

flutter图片资源加载处理

每次都忘了配置这个插件的流程&#xff0c;在此记录一下&#xff1a; 说明&#xff1a;Flutter图片资源路径生成小工具&#xff0c;把assets目录下图片路径归纳转换到R文件. 使用: 如 assets/images/pay/pay_cash.png import ‘r.dart’;\ …\ Image.asset(R.pay_cash) 先安装插…

【计算机视觉前沿研究 热点 顶会】ECCV 2024中目标检测有关的论文

整值训练和尖峰驱动推理脉冲神经网络用于高性能和节能的目标检测 与人工神经网络(ANN)相比&#xff0c;脑激励的脉冲神经网络(SNN)具有生物合理性和低功耗的优势。由于 SNN 的性能较差&#xff0c;目前的应用仅限于简单的分类任务。在这项工作中&#xff0c;我们专注于弥合人工…

[Git使用] 实战技巧

文章目录 1. 理解分叉点2. Rebase3. FixUp4. Revert5. Squash Commit (仅限于本地操作)5. Git Fetch6. Cheery-Pick1. 理解分叉点 合并分支的时候会产生分叉点 比如: 仓库有dev和feature两个分支; 操作1:dev远程新建一个文件操作2:feature提交第一次操作3:远程执行把fetur…

【深度学习】softmax 回归的从零开始实现与简洁实现

前言 小时候听过一个小孩练琴的故事&#xff0c;老师让他先弹最简单的第一小节&#xff0c;小孩练了两天后弹不出。接着&#xff0c;老师让他直接去练更难的第二小节&#xff0c;小孩练习了几天后还是弹不出&#xff0c;开始感觉到挫败和烦躁了。 小孩以为老师之后会让他从简…