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

news2024/11/14 15:01:39

文章目录

  • 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的代码:

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的代码:

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>() {
            @Override
            public 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方法来完成加锁和解锁.

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把操作系统的信号量封装了一下~

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来实现类似加锁的效果.

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);
        // 设置可用资源数为1
        Semaphore 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来达成这个效果.

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.coloradmin.cn/o/2115671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SprinBoot+Vue校园数字化图书馆系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

Linux 操作系统 进程(1)

什么是进程 想要了解什么是进程&#xff0c;或者说&#xff0c;为什么会有进程这个概念&#xff0c;我们就需要去了解现代计算机的设计框架(冯诺依曼体系)&#xff1a; 计算机从设计之初就以执行程序为核心任务&#xff0c;也就是运算器从内存中读取&#xff0c;也只从内存中…

24款奔驰CLE升级原厂360全景影像效果怎么样

24款奔驰 CLE 轿跑&#xff1a;全景视野&#xff0c;驾驭无忧 24款奔驰 CLE 轿跑&#xff0c;以其优雅的线条和卓越的性能&#xff0c;成为道路上的一道亮丽风景。而升级原厂 360 全景影像&#xff0c;将为您的驾驶增添更多安全与便利。 原厂 360 全景影像的升级&#xff0c;…

算法分享——《双指针》

文章目录 ✅[《移动零》](https://leetcode.cn/problems/move-zeroes/)&#x1f339;题目描述&#xff1a;&#x1f697;代码实现&#xff1a;&#x1f634;代码解析&#xff1a; ✅[《复写零》](https://leetcode.cn/problems/duplicate-zeros/)&#x1f339;题目描述&#xf…

心觉:潜意识是一个免费的“超级工作狂”,你居然不会用

我们常听说&#xff1a;潜意识的力量是意识到3万倍以上 你信吗 估计很多人不相信&#xff0c;不相信当然用不好 不相信的原因核心有两个&#xff1a; 没有体验过 寻求绝对的科学验证 这两个原因会让你对潜意识不相信&#xff0c;或者半信半疑 今天我也不会给你绝对的科学…

基于 Unet-MobileNet 网络实现的腹部肝脏语义分割

1、MobileUnet 网络 Unet是一种卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;通常用于图像分割任务 Unet架构由编码器和解码器组成。编码器负责捕获上下文并从输入图像中提取特征&#xff0c;而解码器负责上采样并生成分割掩模。 Unet中的编码器由多个卷积层组成…

汽车免拆诊断案例 | 捷豹 E-type怠速不稳定

故障现象 一辆3.8E型直列6缸的捷豹&#xff0c;该车的故障原因表现为怠速不稳定&#xff0c;转速不均匀&#xff0c;以及在节气门全开&#xff08;WOT工况&#xff09;时“回火”和加速踏板不能及时提速。这是该车型一个值得关注的典型案例。车主表示&#xff0c;几年前&#…

Python条形码生成

条形码基础知识 在开始编码之前&#xff0c;让我们先了解一下条形码的基本概念。条形码本质上是一种将数据编码成可视模式的方法&#xff0c;通常由一系列平行的黑色条和白色空格组成。常见的条形码类型包括&#xff1a; UPC&#xff08;通用产品代码&#xff09; EAN&#x…

828华为云征文|华为云Flexus X实例部署k3s与kuboard图形化管理工具

828华为云征文&#xff5c;华为云Flexus X实例部署k3s与kuboard图形化管理工具 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务的需求&#xff0c;一定…

数据结构(邓俊辉)学习笔记】排序 6——希尔排序:框架 + 实例

文章目录 1. 策略2.实例3.循秩访问4. 插入排序5.Shell序列 1. 策略 来学习一种非常别致的排序算法&#xff0c;也就是希尔排序。 希尔排序算法既有着悠久的历史&#xff0c;同时也仍然不失活力。该算法的别致之处在于&#xff0c;它不再是将输入视作为一个一维的序列&#x…

SpringTest框架JUnit单元测试用例获取ApplicationContext实例的方法

JUnit单元测试用例中使用Spring框架&#xff0c;之前我的使用方式很直接。 /*** 用于需要用到Spring的测试用例基类* * author lihzh* alia OneCoder* blog http://www.coderli.com*/ RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations { "/sprin…

七. 部署YOLOv8检测器-deploy-yolov8-basic

目录 前言0. 简述1. 案例运行2. 补充说明3. 代码分析3.1 main.cpp3.2 trt_detector.hpp3.2 trt_detector.cpp 4. INT8量化前瞻总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程…

【车载开发系列】ParaSoft入门介绍

【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍一. ParaSoft的背景二. 设计理念三. ParaSoft C/CTest简介四. 具备常用功能1&#xff09;静态代码分析2&#xff09;代码覆盖率分析3&#xff09;模糊测试4&#xff09;自…

协议的认识和理解

目录 1. 协议 1.1. 站在日常生活的角度初始协议 1.2. 网络分层结构 (OS vs 网络) 1.2.1. 软件分层 1.2.2. 网络分层 1.3. 站在语言的角度理解协议 1. 协议 对于协议&#xff0c;我们可以用一句话概括它&#xff1a;协议本质上就是一种约定。 1.1. 站在日常生活的角度初始…

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(四)

概述 在 WWDC 24 中,苹果推出了数据库框架 SwiftData 2.0,并为其加入了全新的 History Trace、“墓碑”等诸多激动人心的新功能。那么它们到底如何实际应用到我们的 App 中去呢? 想搞清楚历史记录追踪(History Trace)如何在 SwiftData 2.0 中大放异彩吗?看这篇就对了! …

压缩文件隐写

1、伪加密 &#xff08;1&#xff09;zip伪加密 考点&#xff1a;winhex打开压缩包&#xff1b;搜索504b0102(注意不是文件头部&#xff1b;zip文件头部伪504b0304);从50开始&#xff0c;往后面数第9&#xff0c;10个字符为加密字符&#xff0c;将其设置为0000即可变为无加密状…

攻防世界--->获取

做题笔记。 下载 查壳 64ida打开 main函数&#xff1a; 查找字符&#xff1a; 根据程序逻辑&#xff0c;创建了一个新文件并且进行了写入。 直接Linux上动调一下。 SharifCTF{b70c59275fcfa8aebf2d5911223c6589}

python安装以及访问openAI API

安装python 我是python小白&#xff0c;所以需要一步一步来&#xff0c;先安装。 一口吃不成胖子&#xff0c;记住。 从官网下载python&#xff0c;目前最新版本是3.12&#xff0c;但是据说稳定版3.11更好一点&#xff0c;所以&#xff0c;下载3.11&#xff0c;注意不要下载…

Hiredis的使用

Hiredis的使用 &#x1f4f8;这里安利一个github仓库介绍 图片生成 Socialify 一键生成专业 GitHub 仓库简介图 一、Hiredis的安装与使用 1、下载hiredis软件包&#xff0c; https://github.com/redis/hiredis.git 或者使用git下载到本地 git clone https://github.com/redi…

Camtasia 2024破解版注册机包含激活码秘钥

&#x1f3ac; 嗨&#xff0c;亲爱的朋友们&#xff01;今天我要给你们安利一款超级炫酷的屏幕录制和视频编辑软件——Camtasia 2024&#xff01;&#x1f389; camtasia2024绿色免费安装包winmac下载&#xff0c;点击链接即可保存。 https://pan.quark.cn/s/5ee0c4655701 C…