多线程—JUC(java.util.concurrent)

news2025/4/23 13:47:27

上篇文章:

多线程—synchronized原理https://blog.csdn.net/sniper_fandc/article/details/146713129?fromshare=blogdetail&sharetype=blogdetail&sharerId=146713129&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

1 Callable接口

2 ReentrantLock

3 原子类

4 线程池

5 信号量Semaphore

6 CountDownLatch


1 Callable接口

        在多线程环境下,如果要计算1+...+100的值,没有学习Callable接口前,可能我们写的代码如下:

public class CallThread {

    public static Object locker = new Object();

    public static int sum = 0;

    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(() -> {

            int result = 0;

            for(int i = 1;i <= 100;i++){

                result += i;

            }

            synchronized (locker){

                sum = result;

                locker.notify();

            }

        });

        t.start();

        synchronized (locker){

            while(sum == 0){

                locker.wait();

            }

            System.out.println("1+...+100 = " + sum);

        }

    }

}

        由于run方法没有返回值,因此必须使用全局变量sum来接收计算结果,同时要输出计算结果,必须得等线程运行结束,因此使用wait和notify方法来保证线程同步。这样的代码是比较繁琐的,如果使用了Callable接口,就会简化很多:

public class CallThread {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        Callable<Integer> callable = new Callable<Integer>() {

            public Integer call() throws Exception {

                int result = 0;

                for(int i = 1;i <= 100;i++){

                    result += i;

                }

                return result;

            }

        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);

        Thread t = new Thread(futureTask);

        t.start();

        System.out.println("1+...+100 = " + futureTask.get());

    }

}

        上述代码的运行结果如下:

        Callable接口类似Runnable接口,也是定义线程需要执行的任务,但是不同的是这个任务可以带返回值,返回值类型就是泛型参数类型。定义任务后,需要用FutureTask来包装这个任务,用于接收Callable实例的返回值。创建线程需要把FutureTask的实例作为参数传入,线程执行结束后,会把计算结果放入FutureTask的实例中。获取FutureTask实例中的返回值需要用到get(),如果线程还没有执行结束,代码会阻塞到get()方法的位置,直到线程执行结束接收到返回值。

2 ReentrantLock

        ReentrantLock直译可重入锁,和synchronized类似,也是通过加锁实现共享数据的互斥访问。

        ReentrantLock类加锁操作的实现方式是通过ReentrantLock的实例.lock()来加锁,通过ReentrantLock的实例.unlock()来解锁:

ReentrantLock lock = new ReentrantLock();

lock.lock();

... 

lock.unlock();   

         但是这种方式往往给开发人员带来更复杂的代码逻辑,导致如果加锁过程中出现异常,可能程序就执行不到解锁,因此常常搭配try-catch-finally语句使用:

ReentrantLock lock = new ReentrantLock();

lock.lock();  

try {    

...

} finally {    

 lock.unlock()    

}

        有了synchronized,为什么还需要ReentrantLock?(它们的区别是什么)

        1.synchronized是关键字,是JVM内部的实现。而ReentrantLock是一个类,是标准库的一个类,是基于java的实现。

        2.synchronized的加锁解锁基于代码块,进入代码块加锁,退出代码块解锁,不需要显示调用加锁解锁的代码。ReentrantLock的加锁解锁基于lock()和unlock(),需要显示调用方法,更灵活,但是解锁容易遗漏。

        3.synchronized是非公平锁。而ReentrantLock默认是非公平锁,但是也提供了公平锁版本,构造方法传参true就会启用公平锁策略。

        4.synchronized如果加锁失败,就会进入阻塞状态。而ReentrantLock提供trylock()方法,这个方法会尝试加锁,如果加锁失败,不会进入阻塞状态并返回false,继续向下执行其他代码。trylock()还可以设置一定的等待时间,如果加锁失败,就等待一会,时间一过再放弃。

     5.synchronized必须使用wait()和notify()来等待和唤醒,唤醒时随机唤醒一个线程。ReentrantLock搭配Condition类实现等待和唤醒,可以随机唤醒一个,也可以唤醒指定线程。

        当锁竞争不激烈时,使用synchronized更好(偏向锁和轻量级锁)。当锁竞争激烈时,使用ReentrantLock更灵活,不让线程死等。如果想要使用公平锁,也要用ReentrantLock。

3 原子类

        原子类基于CAS实现,因此比加锁实现的代码性能更高,主要有以下几个类:AtomicBoolean、AtomicInteger、AtomicIntegerArray、AtomicLong、AtomicReference、AtomicStampedReference。

        常见的方法有:addAndGet(int delta)<=>i += delta、decrementAndGet()<=>--i、getAndDecrement()<=>i--、incrementAndGet()<=>++i、getAndIncrement()<=>i++,这些方法都是AtomicInteger类中的方法。

4 线程池

        线程池的创建最核心的类是ThreadPoolExecutor类,这个类有多种参数,帮助我们创建多种类型的线程池:

        corePoolSize:核心线程的数量。(核心线程一旦创建就一直存在)

        maximumPoolSize:最大线程的数量=核心线程+临时线程的数目。(临时线程:一段时间不干活,就被销毁)

        keepAliveTime:临时线程允许的空闲时间。

        unit:keepaliveTime的时间单位,是秒,分钟等等。

        workQueue:传递任务的阻塞队列。线程池内部有默认的阻塞队列,我们也可以传入自定义的队列。(线程池的可扩展性)

        threadFactory:创建线程的工厂,参与具体的创建线程工作。

        RejectedExecutionHandler:拒绝策略,如果任务量超出线程池的任务队列的负荷了接下来怎么处理。策略有如下四种:

        AbortPolicy():超过负荷,直接抛出异常。

        CallerRunsPolicy():调用者负责处理。

        DiscardOldestPolicy():丢弃队列中最老的任务。

        DiscardPolicy():丢弃新来的任务。

        而前面使用线程池创建线程方法中使用的Executors本质上是ThreadPoolExecutor类的封装。

        注意:创建线程池时线程数量如何设置?但凡回答具体的数字都是错的,正确的做法是根据不同的程序设置不同数量,可以通过压测(性能测试)来控制不同线程数量,观察CPU占用率、内存占用率等等来确定合适的线程数量。

5 信号量Semaphore

        信号量Semaphore表示可用资源的数量,本质上是一个计数器,在信号量机制中有操作P和V(都是原子性的操作),P操作表示申请资源,因此P操作后资源数量-1;V操作表示释放资源,V操作后资源数量+1。当可用资源数量为0时,P操作阻塞,因此通过信号量机制也可以实现线程同步。

        P操作对应的方法是acquire(),V操作对应的方法是release()。具体使用如下:

public class MySemaphore {

    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("获取资源");

    }

}

        创建包含3个资源的计数器,但是main尝试连续获取4个资源,结果是被阻塞到第4次acquire()处。

        如果把代码中的注释取消,即连续3次获取资源,此时可用资源的数量为0,再释放一次资源,此时可用资源的数量为1,再获取1次资源即可顺利执行。

6 CountDownLatch

        CountDownLatch这个类会等待所有的任务都执行结束后再执行后续的代码,具体使用如下:

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(10);

        for(int i = 0;i < 10;i++){

            Thread t = new Thread(() ->{

                try {

                    //Math.random()返回0-1之间的随机数

                    Thread.sleep((long)(Math.random() * 5000));

                    System.out.println(Thread.currentThread().getName() + "执行完毕了");

                    countDownLatch.countDown();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            },"线程"+ i);

            t.start();

        }

        countDownLatch.await();

        System.out.println("所有任务都执行完了,工作结束");

    }

}

        CountDownLatch的构造方法传入的参数表示任务的个数,对应内部有个计数器,每当一个任务调用countDownLatch.countDown(),计数器-1。await()会进行阻塞,直到计数器为0。只有所有任务都调用countDown(),计数器为0,所有任务都执行完毕,此时await()方法才结束阻塞。代码运行结果如下:

下篇文章:

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

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

相关文章

从零开始跑通3DGS教程:(三)坐标系与尺度编辑(CloudCompare)

写在前面 本文内容 本文所属《从零开始跑通3DGS教程》系列文章&#xff1b; sfm重建的点云已经丢掉了尺度信息&#xff0c;并且坐标系跟图像数据有关(SFM初始化选择的图像)&#xff0c;所以如果想恢复物理真实尺度&#xff0c;以及在想要的视角下渲染&#xff0c;那么需要对尺度…

多线程 - 线程安全引入

写一个代码&#xff0c;让主线程创建一个新的线程&#xff0c;由新的线程负责完成一系列的运算&#xff08;比如&#xff1a;1 2 3 ... 1000&#xff09;&#xff0c;再由主线程负责获取到最终结果。 但打印结果为 result 0&#xff0c;略微思考&#xff0c;明白了要让 t 线…

【面试八股】:CAS指令

一、CAS 面试题 1. 说说CAS、CAS有什么问题&#xff08;ABA)?(美团一面&#xff09; Compare And Swap 对比交换&#xff08;原子指令&#xff09; CAS是 CPU指令 操作系统原生 API&#xff0c;JVM对它进行了封装&#xff08;C)&#xff0c;供我们使用。 通过判断 内存 和 …

el-radio-group 中 el-radio-button value未能绑定上数值数据

这样绑定到admin后不会随着admin的值显示 在value加上 : 后成功显示

JSP(实验):带验证码的用户登录

[实验目的] 1&#xff0e;掌握应用request对象获取表单提交的数据。 2&#xff0e;掌握解决获取表单提交数据产生中文乱码的问题。 3&#xff0e;掌握使用response对象进行定时跳转功能。 4&#xff0e;掌握使用session对象完成登录和注销功能。 [实验要求] 设计带验证码…

集多功能为一体的软件,支持批量操作。

今天我给大家分享一个超实用的小工具&#xff0c;真的是太好用了&#xff01;这个软件是吾爱大神无知灰灰制作的&#xff0c;它能直接一键把webp格式的图片转换成png格式。 webp转为png 一键操作&#xff0c;支持压缩 其实&#xff0c;作者最近在工作中经常遇到webp格式的图片…

linux压缩指令

今天我们来了解一下linux压缩指令,压缩是我们文件传输的一种重要手段,对此,我们是必须学习压缩指令的,那么话不多说,来看. 1.grep过滤查找&#xff0c;管道符&#xff0c;“&#xff5c;”&#xff0c;表示将前一个命令的处理结果输出传递给后面的命令处理。 基本语法&#x…

污水处理厂人员定位方案-UWB免布线高精度定位

1. 方案概述 本方案采用免布线UWB基站与北斗卫星定位融合技术&#xff0c;结合UWBGNSS双模定位工卡&#xff0c;实现污水处理厂室内外人员高精度定位&#xff08;亚米级&#xff09;。系统通过低功耗4G传输数据&#xff0c;支持实时位置监控、电子围栏、聚集预警、轨迹回放等功…

Elasticsearch 高级

Elasticsearch 高级 建议阅读顺序&#xff1a; Elasticsearch 入门Elasticsearch 搜索Elasticsearch 搜索高级Elasticsearch高级&#xff08;本文&#xff09; 1. nested 类型 1.1 介绍 Elasticsearch 中的 nested 类型允许你在文档内存储复杂的数据结构&#xff0c;比如一个…

C语言笔记数据结构(链表)

希望文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正,一起交流学习! 目录 1.链表 1.1 链表概念和组成 1.2 链表的分类 1.3 顺序表和链表 2.单链表&#xff08;无头单向不循环链表&#xff09; 2.1 结点的创建 2.2 创建新的结点 2.3 单链表的打印 2.4 尾…

Leetcode 两数相除

✅ LeetCode 29. 两数相除 — 思路总览 &#x1f9e9; 题目要求 给定两个整数 dividend 和 divisor&#xff0c;实现 整数除法&#xff0c;不能使用乘法 *、除法 / 和取余 % 运算符。 要求返回的结果应为 向零截断的整数商&#xff0c;即&#xff1a; 正数向下取整&#xf…

人工智能图像识别Scala介绍

Scala 一.Scala 简介 Scala即Scalable Language&#xff08;可伸缩的语言&#xff09;&#xff0c;Scala 语言是由 Martin Odersky 等人在 2003 年开发的&#xff0c;并于 2004 年首次发布。意味着这种语言设计上支持大规模软件开发&#xff0c;是一门多范式的编程语言。 Sc…

C++中使用CopyFromRecordset将记录集拷贝到excel中时,如果记录集为0个,函数崩溃,是什么原因

文章目录 原因分析解决方案1. 检查记录集是否为空2. 安全调用COM方法3.进行异常捕获4. 替代方案&#xff1a;手动处理空数据 总结 在C中使用CopyFromRecordset将空记录集&#xff08;0条记录&#xff09;复制到Excel时崩溃的原因及解决方法如下&#xff1a; 原因分析 空记录集…

c#的.Net Framework 的console 项目找不到System.Window.Forms 引用

首先确保是建立的.Net Framework 的console 项目,然后天健reference 应用找不到System.Windows.Forms 引用 打开对应的csproj 文件 在第一个PropertyGroup下添加 <UseWindowsForms>true</UseWindowsForms> 然后在第一个ItemGroup 下添加 <Reference Incl…

蓝桥杯嵌入式学习笔记

用博客来记录一下参加蓝桥杯嵌入式第十六届省赛的学习经历 工具环境准备cubemx配置外部高速时钟使能设置串口时钟配置项目配置 keil配置烧录方式注意代码规范头文件配置 模块ledcubemx配置keil代码实现点亮一只灯实现具体操作的灯&#xff0c;以及点亮还是熄灭 按键cubemx配置k…

Blender多摄像机怎么指定相机渲染图像

如题目所说&#xff0c;当blender的场景里面有摄像机的时候&#xff0c;按F12可以预览渲染结果&#xff0c;但是当有多个摄像机的时候就不知道使用哪个进行渲染了。 之前在网上没有找到方法&#xff0c;就用笨方法&#xff0c;把所有的摄像机删除&#xff0c;然后设置自己需要…

从 MySQL 到时序数据库 TDengine:Zendure 如何实现高效储能数据管理?

小T导读&#xff1a;TDengine 助力广州疆海科技有限公司高效完成储能业务的数据分析任务&#xff0c;轻松应对海量功率、电能及输入输出数据的实时统计与分析&#xff0c;并以接近 1 : 20 的数据文件压缩率大幅降低存储成本。此外&#xff0c;taosX 强大的 transform 功能帮助用…

观察者模式:解耦对象间的依赖关系

观察者模式&#xff1a;解耦对象间的依赖关系 JDK 中曾直接提供对观察者模式的支持&#xff0c;但因其设计局限性&#xff0c;现已被标记为“过时”&#xff08;Deprecated&#xff09;。不过&#xff0c;观察者模式的思想在 JDK 的事件处理、spring框架等仍有广泛应用。下面我…

windows第二十章 单文档应用程序

文章目录 单文档定义新建一个单文档应用程序单文档应用程序组成&#xff1a;APP应用程序类框架类&#xff08;窗口类&#xff09;视图类&#xff08;窗口类&#xff0c;属于框架的子窗口&#xff09;文档类&#xff08;对数据进行保存读取操作&#xff09; 直接用向导创建单文档…

通信协议之串口

文章目录 简介电平标准串口参数及时序USART与UART过程引脚配置 简介 点对点&#xff0c;只能两设备通信只需单向的数据传输时&#xff0c;可以只接一根通信线当电平标准不一致时&#xff0c;需要加电平转换芯片&#xff08;一般从控制器出来的是信号是TTL电平&#xff09;地位…