【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch

news2024/9/23 15:32:11

JUC:java.util.concurrent

一、Callable 接⼝

接口方法
Callablecall,带有返回值
Runnablerun,void
所以创建一个线程,希望它给你返回一个结果,那么使用 Callable 更加方便一些

比如,创建一个线程,让这个线程计算:1+2+3+4+…+1000=?

//使用Runnable接口
public class Demo {  
    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;  
            }        
        });        
        t.start();  
        t.join();  
        
        System.out.println(“result=+ result);  
    }
}
  • 当前代码可以解决问题,但不够优雅,必须得引入一个成员变量 result

相比之下,Collable 就可以解决上述问题:

import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.FutureTask;  
  
public class Demo3 {  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
        Callable<Integer> callable = new Callable<Integer>() {  
            @Override  
            public Integer call() throws Exception {  
                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); //Thread  
        t.start();  
        //后续需要通过 futureTask 来拿到最终的结果  
        System.out.println(futureTask.get());  
    }
}
  • Thread 不能直接传入 callable 作为参数,需要传入实例化的 Callable 对象 callable 实例化一个 FutureTask 对象 futureTask,再将这个 futureTask 对象创建出线程
  • 并且后面要拿到 sum 的值,也需要通过 futureTask
  • futureTaskget 操作是带有阻塞功能的
    • 当前 t 线程还没执行完,get 就会阻塞
    • 直到 t 线程执行完之后,get 才会返回

  • 比如说你去吃麻辣烫,夹好菜后,服务员会给你一个“号码牌”,方便你后续取餐。这里通过 FutureTask 实例化出的对象 futureTask 就相当于是号码牌,你通过 Callable 实例出的 callable 对象就相当于是你夹的菜。你是通过你夹的菜(callable)拿到号码牌(futureTask),最后你想要拿到菜,也得通过号码牌(futureTask)拿到

创建线程的方式:

  1. 直接继承 Thread
  2. 使用 Runnable
  3. 使用 Callable
  4. 使用 lambda
  5. 使用线程池

二、ReentrantLock

  • 可重入锁
  • synchronized 只是 Java 提供的一种加锁的方式
  • ReentrantLock 属于经典风格的锁,通过 lockunlock 方法来完成加锁和解锁
import java.util.concurrent.locks.ReentrantLock;

public class Demo {  
    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=" + count);  
    }
}

synchronized 和 ReentrantLock 的差异

实际开发中,大多数情况下,使用 synchronized 即可,但 ReentrantLock 相比于 synchronized 还是有一些差异的

  1. synchronized 属于关键字(底层是通过 JVMC++代码实现的)
    ReentrantLock 测试标准库提供的类,通过 Java 代码实现的

  2. synchronized 通过代码块控制加锁解锁
    ReentrantLock 通过调用 lock/unlock 方法来完成,unlock 可能会遗漏,要确保能执行到 unlock(通常会把 unlock 放在 finally 中)

  3. ReentrantLock 提供了 tryLock 这样的加锁风格(重点

    • 前面介绍的加锁,都是发现锁被别人占用了,就阻塞等待
    • tryLock 在加锁失败的时候,不会阻塞,而是直接返回,并且通过返回值来反馈是加锁成功还是失败
    • 坚持不一定成功,但是放弃了一定轻松~(摆烂态度)
  4. ReentrantLock 还提供了公平锁的实现(重点

    • 它默认是非公平的,但可以在构造方法中,将参数设为 true,将其设为公平锁
  5. ReentrantLock 还提供了功能更强的“等待通知机制”

    • 基于 Condition 类,功能要比 wait/notify 更强一些

三、信号量 Semaphore

也称为“信号灯”(开船的水手,旗语)

你去车库停车,如何知道是否还有空位?
现在的停车场,一般的入口处,就会有一个“电子牌”,会显示有多少个空闲车位

  • 有车开进去了,上述的计数 -1
  • 有车开出来了,上述的计数 +1
  • 如果计数为 0 了,说明没空位了

这里的“电子牌”就像是一个“信号量”,信号量是一个计数器,通过计数器衡量“可用资源”的个数

  • 申请资源(acquire):让计数器 -1(“P”操作
  • 释放资源(release):让计数器 +1(“V”操作
  • 如果计数器为 0 了,继续申请,就会出现阻塞
    所以信号量的操作也称为“PV 操作

操作系统本身提供了信号量实现,JVM 把操作系统的信号量封装了一下,我们直接使用就可以了

import java.util.concurrent.Semaphore;  
  
public class Demo5 {  
    public static void main(String[] args) throws InterruptedException {  
        //参数为可用资源的个数,计数器的初始值  
        Semaphore semaphore = new Semaphore(3);  
  
        semaphore.acquire();  
        System.out.println("申请一个资源1");  
        semaphore.acquire();  
        System.out.println("申请一个资源2");  
        semaphore.acquire();  
        System.out.println("申请一个资源3");  
        semaphore.acquire();  
        System.out.println("申请一个资源4");  
    }
}
//运行结果
申请一个资源1
申请一个资源2
申请一个资源3
  • 初始化的信号量为 3
  • 申请了三个资源后,没空位了,计数器为 0 了,就堵塞住了

若在这里面释放一次资源,就可以将资源 4 申请进去:

import java.util.concurrent.Semaphore;  
  
public class Demo5 {  
    public static void main(String[] args) throws InterruptedException {  
        //参数为可用资源的个数,计数器的初始值  
        Semaphore semaphore = new Semaphore(3);  
  
        semaphore.acquire();  
        System.out.println("申请一个资源1");  
        semaphore.acquire();  
        System.out.println("申请一个资源2");  
        semaphore.acquire();  
        System.out.println("申请一个资源3");  
        semaphore.release();  
        System.out.println("释放一个资源");  
        semaphore.acquire();  
        System.out.println("申请一个资源4");  
    }
}
//运行结果
申请一个资源1
申请一个资源2
申请一个资源3
释放一个资源
申请一个资源4

平替加锁解锁

  • 在需要加锁的时候,可以设置一个信号量,初始化一个资源
  • 谁要用的时候就申请这个资源,用完之后再释放
  • 这样的话,申请到唯一资源的线程执行操作的时候,就不会有其他的线程进行操作了
import java.util.concurrent.Semaphore;  
  
public class Demo4 {  
    private static int count = 0;  
  
    public static void main(String[] args) throws InterruptedException {  
        Semaphore semaphore = new Semaphore(1);  
        Thread t1 = new Thread(() -> {  
            for (int i = 0; i < 50000; i++) {  
                try {  
                    semaphore.acquire();  
                } catch (InterruptedException e) {  
                    throw new RuntimeException(e);  
                }                
                count++;  
                semaphore.release();  
            }        
        });        
        Thread t2 = new Thread(() -> {  
            for (int i = 0; i < 50000; i++) {  
                try {  
                    semaphore.acquire();  
                } catch (InterruptedException e) {  
                    throw new RuntimeException(e);  
                }                
                count++;  
                semaphore.release();  
            }        
        });        
        t1.start();  
        t2.start();  
        t1.join();  
        t2.join();  
        System.out.println("count=" + count);  
    }
}
  • 这种值为 1 的信号量,就相当一个锁,资源要么是 1,要么是 0,所以也称为“二元信号量

四、CountDownLatch

“锁存器”

很多时候,需要把一个大的任务,拆成多个小的任务,通过多线程/线程池来执行。如何衡量,所有的任务都执行完毕了?

  • 比如“多线程下载”
  • 浏览器的下载,一般是单线程的,下载速度是有限的(一秒 2-3 MB)
  • 但是可以通过多线程的方式提高下载速度
    就可以使用专门的下载工具,通过多个线程,和服务器建立多个网络连接(服务器进行网速限制都是针对一个连接做出的限制),那如果我创建 10-20 个线程,那么下载的总速度就能大幅度提高
  • 多个线程进行一起操作,每个线程下载一部分,所有线程下载完毕再进行拼装
import java.util.concurrent.CountDownLatch;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
  
public class Demo6 {  
    public static void main(String[] args) throws InterruptedException {  
        ExecutorService executorService = Executors.newFixedThreadPool(4);  
  
        //构造方法的个数,就是拆分出来的任务数量  
        CountDownLatch countDownLatch = new CountDownLatch(20);  
  
        for (int i = 0; i < 20; 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 收到了 20 个“完成”,所有的任务就都完成了  
        // await => all wait  
        // await 这个词是计算机术语,“等待所有”  
        countDownLatch.await();  
  
        System.out.println("所有任务都完成");  
    }
}
  • CountDownLatch 一般都是结合线程池进行使用
  • 借助 CountDownLatch 就能衡量出当前任务是否整体执行结束

上面这些再实际开发中用的布套多,但面试可能问到,特别是“ReentrantLock”和“Semaphore”

五、相关面试题

image.png


image.png


image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2046772.html

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

相关文章

leetcode350. 两个数组的交集 II,哈希表

leetcode350. 两个数组的交集 II 给你两个整数数组 nums1 和 nums2 &#xff0c;请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数&#xff0c;应与元素在两个数组中都出现的次数一致&#xff08;如果出现次数不一致&#xff0c;则考虑取较小值&#xff09;。可…

导出word格式的Javadoc(可用于快速生成项目详细设计文档)

导出word格式的Javadoc ​ 最近要编写项目详细设计文档&#xff0c;作为程序员当然想看看有没有能够自动生成的办法&#xff0c;生成详细设计文档&#xff0c;然后再在生成的基础上略做修改就好了&#xff08;偷懒大法~&#xff09;&#xff0c;还真有&#xff0c;特此分享&am…

数字乡村+智慧农业数字化转型大数据平台建设方案

1. 数字农业发展趋势 数字农业正经历全环节数字技术应用、全流程生产经营再造、全方位线上线下对接和管理服务全生命周期覆盖的四大趋势&#xff0c;标志着我国农业进入高质量发展新阶段。 2. 数字乡村的战略意义 数字乡村作为数字化、网络化和信息化的产物&#xff0c;对于…

人工智能技术工程师由谁颁发?都学习哪些课程?

人工智能技术工程师是一个充满挑战和机遇的职业。他们将用自己的专业知识和技能&#xff0c;为人类带来更加美好的未来。 一、人工智能工程师发证单位是谁&#xff1f; 人工智能技术工程师证书是由工业和信息化部教育与考试中心颁发的。 工信部电子标准院的人工智能从业人员认…

基于飞腾平台的Hadoop的安装配置

【写在前面】 飞腾开发者平台是基于飞腾自身强大的技术基础和开放能力&#xff0c;聚合行业内优秀资源而打造的。该平台覆盖了操作系统、算法、数据库、安全、平台工具、虚拟化、存储、网络、固件等多个前沿技术领域&#xff0c;包含了应用使能套件、软件仓库、软件支持、软件适…

旅游卡真伪一招辨别,避免旅行陷阱!

揭秘旅游卡真伪&#xff01;一招教你识别隐藏陷阱&#xff0c;避免旅行大坑&#xff01; 对于热爱旅行的朋友们来说&#xff0c;旅游卡无疑是一种方便又实惠的选择。 然而&#xff0c;随着市场上的旅游卡种类越来越多&#xff0c;如何选择一张真实有效的旅游卡成为了大家关注…

CI/CD 自动化:最大限度地提高极狐GitLab 群组的“部署冻结”影响

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

矩阵获客时代,如何有效管理云微客短视频矩阵?

随着短视频内容数量的快速增长&#xff0c;一站式、高效、智能化的矩阵管理模式&#xff0c;正逐渐受到各个行业的关注和喜爱。那么该如何有效地管理短视频矩阵&#xff0c;确保内容的高质量、高效率和准确投放呢&#xff1f; 当今数字化时代&#xff0c;短视频已经成为了一种极…

Oracle 用户-表空间-表之间关系常用SQL

问题&#xff1a; 当某一个表数据量特别大&#xff0c;突然插入数据一直失败&#xff0c;可能是表空间不足&#xff0c;需要查看表的使用率 用户-表空间-表之间关系&#xff1a;用户可以有多个表空间&#xff0c;表空间可以有多个表&#xff0c;表只能拥有一个表空间和用户 1.…

联邦学习:破解农业大数据共享的安全难题

&#xff08; 于景鑫 国家农业信息化工程技术研究中心 &#xff09;农业大数据是农业智能化的基石,其共享与开放是发掘数据价值、驱动农业变革的关键。然而,数据隐私与安全问题如同数据共享之路上的一道坎,牵制着农业大数据的流动与融合。联邦学习作为一种颠覆性的分布式机器学…

1Panel应用推荐:Bytebase开源数据库DevOps解决方案

1Panel&#xff08;github.com/1Panel-dev/1Panel&#xff09;是一款现代化、开源的Linux服务器运维管理面板&#xff0c;它致力于通过开源的方式&#xff0c;帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用&#xff0c;1Panel特别开通应用商店&am…

技术爱好者完全用台式机部件定制游戏笔记本电脑

高端笔记本电脑的功能强大到令人难以置信的地步&#xff0c;但大多数笔记本电脑在至少几个关键性能方面仍然落后于台式机。一位 YouTuber 对这种情况感到厌倦&#xff0c;为了抹除这种差距&#xff0c;他开始了为期 14 个月的旅程&#xff0c;使用真正的台式机硬件打造自己的笔…

基于R语言遥感随机森林建模与空间预测;遥感数据处理与特征提取;数据分析与可视化

目录 第一章 理论基础与数据准备【夯实基础】 第二章 随机森林建模与预测【讲解实践】 第三章 实践案例与项目 更多应用 随机森林作为一种集成学习方法&#xff0c;在处理复杂数据分析任务中特别是遥感数据分析中表现出色。通过构建大量的决策树并引入随机性&#xff0c;随…

ubuntu 24.04 软件源配置,替换为国内源

ubuntu 默认的官网源下载速度非常慢&#xff0c;新装 ubuntu 系统首先把 apt 软件源替换成国内源。 1、使用软件和更新设置国内源 打开软件和更新&#xff0c;选择位于中国的服务器&#xff1a; 外链图片转存失败&#xff0c;源站可能有防盗链机制&#xff0c;建议将图片保存…

JavaEE 第11节 定时器

前言 本篇博客重点介绍定时器的简单实现&#xff0c;帮助理解其底层原理。关于JAVA工具类自带的定时器&#xff0c;只会简单介绍&#xff0c;详细使用参阅官方文档&#xff08;下文中有官方文档的连接&#xff09;。 一、什么是定时器 定时器的概念非常简单。 它在软件开发…

Umi-OCR 图片文字智能识别软件,不需联网,Linux 下运行错误修复,AI 识文一文搞定!

Umi-OCR 图片文字智能识别软件&#xff0c;不需联网&#xff0c;Linux 下运行除错修复&#xff0c;AI 识文一文搞定&#xff01; Umi-OCR 是一款开源的图片文字只能识别软件&#xff0c;支持多国语言识别&#xff0c;支持截图OCR / 批量OCR / PDF识别 / 二维码 / 公式识别。 …

C:数组传参的本质

1、一维数组传参的本质 数组传参是指在函数调用时将数组作为参数传递给函数。 int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };test(arr);return 0;}数组传参只需要写数组名就可以了。注意&#xff1a;数组名是arr&#xff0c;而不是arr[10] 数组传参形参该怎么写呢&am…

Linux git安装与部署

目录 git安装 1、下载与安装 2、配置git账号信息 创建本地仓库 1、创建本地代码库文件夹 2、创建项目代码本地仓库文件夹 3、进入到projCode目录下&#xff0c;创建git本地仓库 4、创建过滤文件.gitignore 5、添加.gitignore到git暂存区 6、提交.gitignore 7、将项目…

Spring Boot集成钉钉群通知机器人

文章目录 一、钉钉机器人配置1&#xff09;添加机器人2&#xff09;添加自定义机器人3&#xff09;设置机器人参数4&#xff09;添加机器人完成 二、依赖导入三、工具类封装四、关键字推送消息测试类1&#xff09;测试类2&#xff09;程序输出结果&#xff1a;3&#xff09;通知…

基于模糊神经网络的金融序列预测算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于模糊神经网络的金融序列预测算法matlab仿真,根据序列的MAD,RSI,KD等指标实现序列的预测和最终收益分析。 2.测试软件版本以及运行结果展示 MATLAB2022A版本…