javaEE-多线程进阶-JUC的常见类

news2025/1/5 14:43:08

juc:指的是java.util.concurrent包,该包中加载了一些有关的多线程有关的类。

目录

一、Callable接口

FutureTask类

参考代码:

二、ReentrantLock 可重入锁

ReentrantLock和synchronized的区别:

1.ReentantLock还有一个方法:tryLock:尝试上锁。

2.ReentantLock可以实现公平锁

3,ReentrantLock和synchronized的等待通知机制不同

三、semaphore 信号量

四、CountDownLatch 同时等待多个任务执行结束

五、实现线程安全的方式总结

六、创建线程的方式总结

七.线程安全的集合类

1.多线程环境使用集合:ArrayList

1>.用synchronized加锁,保证线程安全.

2>.使用Collectios.synchronizedList(new ArrayList)

3>.使用CopyOnWriteArrayList  写时拷贝

2.多线程环境使⽤队列

1>.自己加锁保证线程安全。

2>.使用阻塞队列BlockingQueue(线程安全的).

3.多线程环境使用哈希表

1>.HashTable

2>.ConcurrentHashMap:并发哈希表



一、Callable接口

Callable是一个接口,将线程封装在方法中,带有一个返回值,用于实现计算,并获取结果类型的任务。相当于把线程封装了⼀个"返回值".⽅便借助多线程的⽅式计算结果.它的作用与Runnable类似.

计算1+2+3+...+1000:

按照之前的方法,要先定义一个成员变量,在一个线程中进行累加,并返回累加的结果,还要用join等该线程执行结束,之后才能得到结果:

这种方式感觉不太美观,Callable是一个接口,就是处理这种带有返回值的多线程任务的;

Callable是一个泛型接口,指定的类型就是要返回结果的类型。

通过 匿名内部类 实现该接口的call方法,来完成要实现的任务:

FutureTask类

在Thread类中,并没有提供构造函数来传入Callable。但JVM提供了另一个类,FutureTask类:未来任务,作为Thread类和Callable接口的 “粘合剂”,

FutureTask提供了可以传Callable的构造方法,且Thread提供了可以传FutureTask类的构造方法,这就可以将两者结合起来;FutureTask类也是泛型类,指定的类型就是传入的Callable的返回类型:

对FutureTask类的理解:

Callable完成任务是需要时间的,该任务是在未来完成的,最后取结果的时候 需要一个凭据,FutureTask 就是这个凭据。

通过调用FutureTask的get方法,可以得到Callable的返回值;get方法是带有阻塞的,当传入的Callable没有完成任务,返回数据时,get方法就处于阻塞等待状态。

参考代码:


/**
 *  Callable接口 带有返回值
 *   通过匿名内部类 ,重写它的 call方法完成任务 
 *  通过 FutureTask类 将Thread与Callable连接
 */
    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 = 1; i <= 1000; i++ ){
                    sum +=i;
                }
                return sum;
            }
        };
//        Thread thread = new Thread(callable);
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("sum= "+ futureTask.get());
    }
    //普通方法:
    private static int sum=0;//定义一个 静态成员变量
    public static void main2(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for(int i = 1;i <= 1000;i++){
                sum += i;
            }
        });
        t.start();
        t.join();//要等t线程执行结束
        System.out.println("sum= " + sum);
    }

Callable和Runnable相比,Callable在实现计算,并有返回值的任务时,代码看上去更美观,别的和Runnable功能是一样的。

二、ReentrantLock 可重入锁

可重入锁,和synchronized锁类似。

这个锁提供了两个方法:lock(上锁) unlocker(解锁)

使用这个锁的时候要注意解锁代码的放置位置;上锁后,在代码执行过程中,遇到return 或者异常终止了,就可能引起 unlock没有被执行,锁没有释放。因此,正确使用ReentrantLock锁 是把unlock放在 finall代码块中,这样就能防止 锁未被释放了。

ReentrantLock和synchronized的区别:

1.ReentantLock还有一个方法:tryLock:尝试上锁。

lock:直接加锁,加锁不成功,就进行阻塞;tryLock:尝试上锁,上锁不成功,返回false,不会进入阻塞状态。提供了更多的可操作空间。

创建两个线程t1,t2;对两个线程都用tryLock上锁,t1线程先获取到了锁,并返回true;t2未获取到锁,返回了false,但并未阻塞,而是继续执行代码,打印了"Thread 02",因为未加锁成功,也就无法解锁,t2的unlock就抛出了异常;之后,t1的休眠时间到,打印“Thread 01",解锁。

2.ReentantLock可以实现公平锁

ReentantLock提供的实现公平锁的方法,通过一个队列,按照先来后到的方式,将线程放到队列中,按照队列中的方式执行。

3,ReentrantLock和synchronized的等待通知机制不同

synchronized通过 wait/notify 来实现;

ReentrantLock通过Condition方法来实现,

三、semaphore 信号量

信号量,⽤来表⽰"可⽤资源的个数".本质上就是⼀个计数器.

就类似停车场:用N记录当前可停车位,

有车进来停车,N-1;有车开走,N+1。这个N就表示可用资源的个数。

设“可⽤资源的个数"用 N来表示:

申请一个资源,会使N-1,称为“P操作”;释放一个资源,会使N+1,称为“V操作”。如果N为0了,继续P操作,则会进行阻塞

信号量是操作系统内部提供的一种机制,操作系统对应的api被JVM封装下,就能通过java代码来调用其相关的操作了。

在java中,用 acquire方法,表示申请;release方法,表示释放

锁就是一种特殊的信号量,可以认为是计数值为1的信号量:释放锁状态,就是1;加锁状态,就是0。对于这种非0即1的信号量,称为二元信号量

Semaphore( n ):传参数的构造方法,参数表示可用资源的个数. 当n=1时(也就相当于一个锁了),通过acquire申请资源,打印“信号量1”,再申请资源,就会进入阻塞状态。

四、CountDownLatch 同时等待多个任务执行结束

CountDownLatch是多线程在特定场景中使用的一个小工具:功能与join类似,join是等待一个进程结束;CountDownLatch 可同时等待 N个任务执行结束。

 代码案例:同时等待10个线程结束, 创建10个线程,同时执行下载任务,main线程 等待结束.

 /**
 * CountDownLatch:可同时等待多个线程结束
 * 设同时等待10个线程结束
 * 创建10个线程,同时下载,等待结束
 */
public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);//10表示:同时等待10个线程结束
        for(int i=0;i<10;i++){ //创建10个线程 负责下载
            int n=i;
            new Thread(()->{
                System.out.println("开始下载: "+n);
                Random random = new Random();
                long s=(random.nextInt(5)+1)*1000;
                try {
                    Thread.sleep(s);//每个线程设置随机休眠时间
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("结束下载: "+n);
                countDownLatch.countDown();//执行完一个线程,就记录一下
            }).start();
        }
        countDownLatch.await();
        System.out.println("main ");
    }

执行结果:

五、实现线程安全的方式总结

1.synchronized锁

2.ReentrantLock可重入锁

3.CAS原子类

4.Semaphore信号量

六、创建线程的方式总结

1.通过继承Thread类及其匿名内部类方式

2.通过实现Runnable接口及匿名内部类方式

3.基于lambda方式

4.基于线程池

5.通过实现Callable接口方式创建

七.线程安全的集合类

Vector,Stack,HashTable,是线程安全的(不建议⽤),其他的集合类不是线程安全的.

1.多线程环境使用集合:ArrayList

保证在多线程安全下的使用方式:

1>.用synchronized加锁,保证线程安全.

2>.使用Collectios.synchronizedList(new ArrayList)

通过Collections类,关键位置都加上synchronized锁,保证线程安全. synchronizedList是标准库提供的⼀个基于synchronized进⾏线程同步的List.synchronizedList 的关键操作上都带有synchronized

相当于给ArrayList加了个壳,加壳后新的集合 list 就是线程安全的了.

3>.使用CopyOnWriteArrayList  写时拷贝

CopyOnWrite容器即写时复制的容器。

当多个线程只进行读操作的时候,不会产生线程安全问题;当要对数组修改时,会先将顺序表复制一benneg份,修改新的表中的内容,再将引用指向新的数组.(这里的操作是原子的,不用加锁)

使用CopyOnWriteArrayList 的利弊:

优点:在多读少写的情况下,无需加锁就解决了ArrayList的线程安全问题,提高了性能。

缺点:对数组的修改不能太频繁;数组不能太长,这些可能会导致复制操作成本太高。

2.多线程环境使⽤队列

1>.自己加锁保证线程安全。

2>.使用阻塞队列BlockingQueue(线程安全的).

3.多线程环境使用哈希表

HashMap是线程不安全的,HashTable是线程安全的duoxianceh

多线程环境使用哈希表可以使用:

1>.HashTable

2>.ConcurrentHashMap:并发哈希表

HashTable是把关键方法上都加了synchronized锁,也就是一个线程对数组中某条链表操作时,别的线程都不能对该数组操作,HashTable在多线程下的执行效率是很慢的。

ConcurrentHashMap: 对HashTable进行了改进和优化

1>.优化了加锁方式

对读操作没有加锁,(只是使用Volatile修饰,确保从内存读数据,读到的是刚修改过的数据)只对写操作进行加锁,且缩小了锁的粒度,不再将整个数组都加锁,对每个链表都分配了一把锁(将每个链表的头节点对象设为锁),只有当多个线程访问同一个链表时,才会产生锁冲突。这样就降低了锁冲突,提高了效率。

2>.充分利用CAS原子操作特性

⽐如size属性通过CAS来更新.避免出现重量级锁的情况.

3>.优化了扩容方式

HashTable通过计算负载因子,判断是否需要扩容,达到要扩容的值,就直接扩容:创建新数组,将原来的数据全复制到新数组中。当数据量非常的时,扩容操作会进行的比较慢,表现出来的就是在运行的某一时刻比较慢,不具有稳定性。

ConcurrentHashMap对此进行了优化,通过“化整为零”方式进行扩容,不是一下将全部数据进行拷贝,而是进行分批拷贝

当需要扩容时,先创建一个新的数组,每次将一部分数据拷贝到新数组中,后续每个来操作ConcurrentHashMap的线程,都会参与搬家的过程.每个操作负责搬运⼀⼩部 分元素.这个过程中新老哈希表都存在,扩容结束,删除旧表;

扩容期间,进行插入操作:直接向新数组中进行插入;

                删除操作:对新老数组都进行删除操作;

                查找操作:对新老数组都进行查找操作;

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

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

相关文章

fpga系列 HDL:ModelSim显示模拟波形+十进制格式数值(临时方法和设置持久化的默认值)

模拟波形 FPGA中使用数字滤波器时&#xff0c;可通过观察模拟波形更好地查看滤波效果。可以通过ModelSim中的波形格式设置来实现更直观的波形显示。右键波形->Format-> Analog 效果 数值格式显示 不同的数值格式显示&#xff1a;右键波形->Radix-> Decimal 效果…

Linux 中 sysctl 和 systemctl 有什么区别?

sysctl 和 systemctl 是两个不同的命令行工具&#xff0c;它们在 Linux 系统中分别用于不同的目的。理解这两个命令的区别对于系统管理和配置非常重要。 1. sysctl 功能 用途&#xff1a;sysctl 用于动态地修改内核参数&#xff0c;这些参数控制着操作系统的某些行为。配置文…

【ArcGISPro/GeoScenePro】检查并处理高程数据

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 数字高程模型 (DEM) 是一种栅格,可显示地面或地形的高程。 数字表面模型 (DSM) 是另一种高程栅格,可显示表面的高度,例如建筑物或树冠的顶部。 您需要准备 DEM 和 DSM 以供分析…

Redis数据库主要数据结构类型

Redis数据库提供了丰富多样的数据结构类型&#xff0c;以满足不同场景下的数据存储需求。以下是Redis中的主要数据结构类型&#xff1a; 一、五种基础数据结构 字符串&#xff08;String&#xff09; 简介&#xff1a;字符串是Redis最基本的数据类型&#xff0c;可以存储字符串…

基于Springboot + vue实现的校园周边美食探索及分享平台

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(1):Oracle Dataguard 概述

Oracle Dataguard&#xff08;主库为 Oracle 11g 单节点&#xff09;配置详解&#xff08;1&#xff09;&#xff1a;Oracle Dataguard 概述 目录 Oracle Dataguard&#xff08;主库为 Oracle 11g 单节点&#xff09;配置详解&#xff08;1&#xff09;&#xff1a;Oracle Data…

mapbox基础,测面功能实现

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️Turf 框架二、🍀测面功能实现1. ☘️实现思路2. ☘️代码样例一、🍀…

基于下垂控制的构网变换器功率控制【微电网变流器】【Simulink】

目录 主要内容 理论研究 整体模型 PQ计算模块 功率控制模块 PWM反馈模块 结果一览 下载链接 主要内容 该仿真针对微电网中分布式电源接入后产生的谐波影响&#xff0c;除了污染网络外&#xff0c;还会恶化微电网变流器输出电流&#xff0c;为了消除谐波影响&a…

2025差旅平台推荐:一体化降本30%

医药行业因其高度专业化的特点&#xff0c;同时在运营过程中又极为依赖供应链和销售网络&#xff0c;因此差旅管理往往成为成本控制的重要环节。本期&#xff0c;我们以差旅平台分贝通签约伙伴——某知名药企为例&#xff0c;探讨企业如何通过差旅一体化管理&#xff0c;在全流…

【漫话机器学习系列】027.混淆矩阵(confusion matrix)

混淆矩阵&#xff08;Confusion Matrix&#xff09; 混淆矩阵是机器学习中评估分类模型性能的一种工具&#xff0c;特别是在多类别分类问题中。它通过对比模型预测结果和真实标签&#xff0c;帮助我们理解模型的分类效果。 1. 混淆矩阵的结构 混淆矩阵通常是一个二维表格&am…

【AIGC】 ChatGPT实战教程:如何高效撰写学术论文引言

&#x1f4a5; 欢迎来到我的博客&#xff01;很高兴能在这里与您相遇&#xff01; 首页&#xff1a;GPT-千鑫 – 热爱AI、热爱Python的天选打工人&#xff0c;活到老学到老&#xff01;&#xff01;&#xff01;导航 - 人工智能系列&#xff1a;包含 OpenAI API Key教程, 50个…

redis的学习(二)

4 哈希表 哈希类型中的映射关系通常称为field-value&#xff0c;⽤于区分Redis整体的键值对&#xff08;key-value&#xff09;&#xff0c; 注意这⾥的value是指field对应的值&#xff0c;不是键&#xff08;key&#xff09;对应的值&#xff0c; 4.1 操作命令 hset&#xff…

IT运维的365天--024 闲置路由器关闭了dhcp,如何知道它的IP是啥

有时候各种原因&#xff0c;我们关闭了路由器的Dhcp&#xff0c;比如需要获取的无线IP和有线同一个网段的情况。时间久了&#xff0c;如果没做标记&#xff0c;大部分时候就会忘了路由器原来设置的是什么IP&#xff0c;没有路由器的对应IP&#xff0c;自然也无法进路由器后台去…

统信系统设置代理的问题

统信系统设置代理的问题 问题表现方式一方式二 问题表现 统信系统下有系统代理和应用代理两个代理。设置系统代理时&#xff0c;git不能经过代理拉取代码。但是设置应用代理时&#xff0c;可以用git通过代理拉代码。 这是系统代理&#xff0c;在这里设置 ip 端口&#xff0c;…

U盘提示格式化?原因、恢复方案与预防措施全解析

一、U盘提示格式化现象概述 在日常使用U盘的过程中&#xff0c;我们有时会遇到一个令人头疼的问题——U盘插入电脑后&#xff0c;系统却弹出一个提示框&#xff0c;告知我们U盘需要格式化才能访问。这个提示往往伴随着数据的潜在丢失风险&#xff0c;让我们不禁为之心焦。U盘提…

Hack The Box-Starting Point系列Responder

答案 When visiting the web service using the IP address, what is the domain that we are being redirected to?&#xff08;当使用IP地址浏览网站时&#xff0c;我们被重定向到了哪个站点&#xff1f;&#xff09; unika.htbWhich scripting language is being used on …

网络物理互连

案例简介 美乐公司为新创建公司&#xff0c;公司现需要架设网络&#xff0c;需要下属分公司通过路由器与外网服务器联通&#xff0c;请使用Packet Tracer&#xff0c; 按照任务要求完成实验。实验中需配置设备或端口的IP地址。 1、绘制拓扑图 2、配置ip地址 3、配置路由ip R0 …

GDB:条件断点:判断相等时使用一个等号还是两个等号

GDB&#xff1a;条件断点&#xff1a;判断相等时使用一个等号还是两个等号 这其实是一个特别简单的问题&#xff0c;网上不同的人分享的也不一样&#xff0c;有的例子用“”&#xff0c;有的例子用“”。 用最简单的helloworld来实验一下&#xff1a; #include <stdio.h&…

TypeScript 常用类型

文章目录 1. 类型注解2. 原始类型3. 数组类型4. 联合类型5. 类型别名6. 函数类型7. 对象类型8. 接口类型8.1 接口声明8.2 接口继承 9. 元组类型10. 类型断言11. 字面量类型12. 枚举类型12.1 数字枚举12.2 字符串枚举 13. any 类型14. typeof 运算符 1. 类型注解 前言&#xff1…

路由基本配置实验

路由器用于实现不同类型网络之间的互联。 路由器转发ip分组的基础是路由表。 路由表中的路由项分为直连路由项、静态路由项和动态路由项。 通过配置路由器接口的ip地址和子网掩码自动生成直连路由项。 通过手工配置创建静态路由项。 热备份路由器协议允许将由多个路由器组…