多线程(进阶三:JUC)

news2024/9/27 17:26:22

目录

一、Callable接口

1、创建线程的操作

2、编写多线程代码

(1)实现Runnable接口(使用匿名内部类)

(2)实现Callable接口(使用匿名内部类)

二、ReentrantLock

1、ReentrantLock和synchronized的区别

2、如何选择使用哪个锁?

三、原子类

四、线程池

五、信号量 Semaphore

代码示例

六、CountDownLatch

代码示例

七、相关面试题

1、线程同步的方式有哪些?

2、为什么有了synchronized还需要juc下的lock?

3、AtomicInteger的实现原理是什么?

4、信号量听说过么?之前都用在过哪些场景下?

5、解释⼀下ThreadPoolExecutor构造方法的参数的含义


JUC即java.utill.concurrent,里面放了一些多线程编程时有用的类,下面是里面的一些类。

一、Callable接口

1、创建线程的操作

        多线程编程时,创建线程有以下五种操作:

1、继承Thread类(包含了匿名内部类的方式)

2、实现Runnable接口(包含了匿名内部类的方式)

3、基于lambda表达式

4、基于Callable接口

5、基于线程池

        为什么有那么多方式可以创建线程,前面三个创建线程很方便,也经常用,为啥还要学Callable接口创建线程的方式呢?答案是因为有它独特的优势和特性。

以下是Callable和Runnable的区别:

                Runnable关注的是这个的过程,也就是重新run方法里面的内容,它的返回值是void。

                Callable即关注过程,也关注结果,Callable提供call方法,返回值就是执行任务得到的结果。

2、编写多线程代码

创建一个线程,这个线程完成1+2+3+...+1000的任务,并打印出结果。

(1)实现Runnable接口(使用匿名内部类)

代码如下:

public class ThreadDemo1 {
    private static int sum = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            int result = 0;
            @Override
            public void run() {
                for (int i = 1; i <= 1000; i++) {
                    result += i;
                }
                sum = result;
            }
        });
        t.start();
        t.join();
        System.out.println("sum = " + sum);
    }
}

执行结果:

可以看到,用实现Runnable接口的代码可以完成任务,但并不优雅,因为要创建一个全局的静态变量,如果其他场景下使用Runnable接口,需要创建很多这样的变量,就容易混淆、记错这些变量的代指。

(2)实现Callable接口(使用匿名内部类)

代码如下:

public class ThreadDemo2 {
    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;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        int result = futureTask.get();
        System.out.println("result = " + result);
    }
}

执行结果如下:

和预期值一样。

实现Callable接口,没有创建变量也可以完成任务,通过call方法的返回值,输出我们想要的值。

注意:

1、这里的FutureTask,因为Thread里面的构造方法的参数没有Callable接口,但有FutureTask类,所以成为了Thread和Callable的粘合剂

2、FutureTask直接翻译的意思是:未来的任务那么未来的任务肯定没有执行完,最终取结果的时候就需要一个凭据,而futureTask就是凭据;就像我们吃麻辣烫的时候,付完款拿到的小牌子,当前工作人员没做完麻辣烫,需要做完后叫到我们的号才能取餐。

3、FutureTask的get方法有阻塞功能,如果线程没有执行完,get就会阻塞,等线程执行完了,return了结果,才会执行get方法返回值。

Callable是一个“锦上添花”的东西,Callable能干的事,Runnable也能干,但对于这种带返回值的任务,使用Callable会更好,代码更直观、简单,但需要理解这里FutureTask起到的作用。


二、ReentrantLock

        ReentrantLock是可重入互斥锁,跟synchronized定位类似,都是实现互斥的效果,保证线程安全。在java的远古时期,sychronized锁的功能没有那么强大,没有各种优化,ReentrantLock就是可以用来使用可重入锁的(历史遗留)。

        传统的锁的风格,锁对象提供两个方法:lock和unlock,这个写法就容易忘记解锁unlock,或者在unlock之前,提前return了,可能引起unlock没执行到,所以正确使用ReentrantLock锁,unlock操作要放进finally。

1、ReentrantLock和synchronized的区别

        那么有了synchronized,为啥还要有ReentrantLock呢?有以下三点(也是synchronized与ReentrantLock的区别):

        1、ReentrantLock提供了tryLock操作。synchronized锁直接进行加锁,加锁不成功就会阻塞;ReentrantLock锁进行加锁时,进行加锁时,如果加锁不成功,不会阻塞,直接返回false,这里的tryLock的操作空间更大。

        2、ReentrantLock锁是公平锁。synchronized锁是非公平锁。

        3,、ReentrantLock和synchronized搭配的等待机制不同。synchronized锁搭配的是wait、notify,而ReentrantLock锁搭配的是Condition类,功能比wait、notify略强一点。

2、如何选择使用哪个锁?

        (1)当锁竞争不激烈时,使用synchronized锁,效率更高,自动释放更方便。

        (2)锁竞争激烈时,使用ReentrantLock,搭配tryLock更加灵活控制加锁行为,而不是死等

        (3)如果要使用公平锁,就使用ReentrantLock。


三、原子类

原⼦类内部⽤的是CAS实现,所以性能要比加锁实现i++高很多。原⼦类有以下几个
• AtomicBoolean
• AtomicInteger
• AtomicIntegerArray
• AtomicLong
• AtomicReference
• AtomicStampedReference

以AtomicInteger举例,常见方法有
addAndGet(int delta);        i += delta
decrementAndGet();          --i
getAndDecrement();          i--
incrementAndGet();          ++i
getAndIncrement();           i++

CAS的详细介绍地址:多线程(进阶二:CAS)-CSDN博客


四、线程池

详细介绍地址:多线程(初阶九:线程池)-CSDN博客


五、信号量 Semaphore

        信号量,用来表示“可用资源的个数”,本质还是一个计数器。而信号量可以理解成停车场的展示牌:当前有100个停车位,相当于有100个可用资源。

        1、当有车开进去的时候,就相当于申请一个可用资源,可用车位就-1(这个称为信号量的P操作)。

        2、当有车开出去的时候,就相当于释放一个可用资源,可用车位就+1(这个称为信号量的V操作)。

        3、当停车场停满车的时候,也就是计数器值为0了,如果还尝试申请资源,就会阻塞等待,知道有其他线程释放资源。

注意:所谓的锁,本质也是信号量,这个信号量比较特殊,可以理解成计数器值为1的信号量。

        锁处于释放状态,计数器值就是1;锁处于加锁状态,计数器值就是0。对于这种非0即1的信号量,称为 “二元信号量”

代码示例

        用两个线程一起完成count自增10000次操作。

public class ThreadDemo1 {
    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 < 5000; i++) {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
                semaphore.release();;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; 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、使用synchronized锁

        2、使用ReentrantLock锁

        3、CAS原子类操作

        4、使用信号量Semaphore


六、CountDownLatch

        CountDownLatch是针对特定场景的小工具。例如:多线程执行任务,把一个大的任务拆分成一个个的小任务,由每个线程执行这些小任务,等执行完全部的小任务,再进行一个汇总,从而完成这样的大任务。那么我们是怎么知道啥时候所有的小任务都完成了呢?如果使用join是无法感知到的,这时候就可以使用CountDownLatch这样的小工具了,它能感知到所有的小任务都完成。

        有这样的多线程下载软件,像idm,基本可以下载任何网站的电影,如果使用浏览器默认的下载方式没有那么快,但idm不一样,因为是多线程下载,可以下载的很快,最终完成后把所有的内容都拼接在一起,就是使用CountDownLatch这样的小工具,可以感知到啥时候这些小任务都下载完了。

代码示例

        创建出5个线程下载一个任务,把这个任务分成5个小任务,5个线程进行下载,都下载往后,打印下载完成。

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //1、此处的构造方法写10,意思是有10个线程/任务
        CountDownLatch latch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            int id = i;
            Thread t = new Thread(() -> {
                Random random = new Random();
                //[0,5)
                int time = (random.nextInt(5) + 1) * 1000;
                System.out.println("线程" + id + "开始下载");
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程" + id + "结束下载");
                //2、告知CountDownLatch我执行完了
                latch.countDown();
            });
            t.start();
        }
        //3、通过这个await操作来等待所有任务结束,也就是countDown被调用10次了
        latch.await();
        System.out.println("所有任务都下载完成!");
    }
}

执行结果:

注意:

1、构造CountDownLatch实例,初始化5表⽰有5个任务需要完成.
2、每个任务执行完毕,都调用 latch.countDown() .在CountDownLatch内部的计数器同时自
减.
3、主线程中使用 latch.await(); 阻塞等待所有任务执行完毕.相当于计数器为0了.


七、相关面试题

1、线程同步的方式有哪些?

答:synchronized、ReentrantLock、Semaphore、原子类的一些操作等都可以。

2、为什么有了synchronized还需要juc下的lock?

答:以JUC的ReentrantLock为例。

        (1)ReentrantLock提供了tryLock操作,进行加锁时,如果失败则返回false,不会阻塞,搭配使用tryLock使用更灵活,而不是死等。而synchronized加锁失败会阻塞,可能会死等。

        (2)ReentrantLock和synchronized搭配的wait、notify机制不同,ReentrantLock搭配的是Condition类,功能比wait、notify更强,可以精确的控制唤醒某个线程。

        (3)synchronized是非公平锁,ReentrantLock是公平锁。

3、AtomicInteger的实现原理是什么?

基于CAS机制,伪代码如下:

详细过程参考地址:多线程(进阶二:CAS)-CSDN博客

4、信号量听说过么?之前都用在过哪些场景下?

答:信号量,用来表示“可用资源的个数”,本质上是个计数器,停车场的展示牌原理就是使用了信号量。使用过的场景:两个线程完成count变量自增10000次;创建Semaphore实例的时候,构造方法的实参传1,表示计数器值为1,当一个线程自增钱就会申请1个资源——P操作,自增完后就会释放1个资源——V操作;两个线程不会同时自增,不会出现线程安全问题,自增前P操作的计数器-1,计数器值为0,另一个线程不会自增,等当前线程自增完后,V操作,计数器值+1,才能进行自增。可以解决线程安全问题。

5、解释⼀下ThreadPoolExecutor构造方法的参数的含义

参考下面地址内容:多线程(初阶九:线程池)-CSDN博客


都看到这了,点个赞再走吧,谢谢谢谢!

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

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

相关文章

算法:阿里巴巴找黄金宝箱(II)

一、算法描述 题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上&#xff0c;无意中发现了强盗集团的藏宝地&#xff0c;藏宝地有编号从0-N的箱子&#xff0c; 每个箱子上面贴有箱子中藏有金币Q的数量。 从金币数量中选出一个数字集合&#xff0c; 并销毁贴有这些数字的每个箱子&…

校招春招,在线测评一般测试哪些内容?

在校园招聘这一块&#xff0c;很多应届毕业生会相当在乎&#xff0c;对于他们来说&#xff0c;如果在学校期间就找到工作是比较轻松的事情&#xff0c;不用担心毕业之后找工作困难重重&#xff0c;可以稳稳当当毕业。但想要迅速通过招聘也不容易&#xff0c;在校招春招上面&…

2024美赛C题参考论文更新+完整数据集+配套代码

2024美赛C题 &#xff08;文末获取完整版&#xff09; 首先&#xff0c;我们需要对缺失的speed_mph进行插补。缺失值处理是数据预处理的重要环节之一。可以采用均值、中位数或者根据其他相关特征进行预测的方法来填补缺失值。在这里&#xff0c;我们可以考虑使用其他相关的特征…

[C++]类和对象(中)

一:类的六个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。空类中并不是什么都没有&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的成员函数称为…

Multi-bit的实现方法和应用 (下)

书接上回&#xff0c;Multi-bit的实现方法和应用 &#xff08;上&#xff09;&#xff0c;闲言少叙&#xff0c;ICer GO&#xff01; In-place MBFF实现 相较于仅基于逻辑连接的MBFF封装&#xff0c;如果考虑到布局的实际情况&#xff0c;那么就有physical aware的in-place的M…

导出pdf 加密、加水印、加页脚

1.依赖 <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.10</version> </dependency> <dependency> …

前端开发之deepmerge的使用和示例(对象的深度合并)

前端开发之deepmerge的使用和示例 前言使用场景链接效果图vue中简单案例1、安装插件2、示例结果前言 在平时的项目中经常会涉及到对象除了第一层以及下层进行深度合并,本问讲解的是深度合并的插件deepmerge,使用此插件避免通过递归实现一些深度合并所带来的问题 使用场景 …

Django的web框架Django Rest_Framework精讲(二)

文章目录 1.自定义校验功能&#xff08;1&#xff09;validators&#xff08;2&#xff09;局部钩子&#xff1a;单字段校验&#xff08;3&#xff09;全局钩子&#xff1a;多字段校验 2.raise_exception 参数3.context参数4.反序列化校验后保存&#xff0c;新增和更新数据&…

基于SpringBoot Vue超市管理系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

爱快安装和双宽带叠加

文章目录 一、爱快安装二、爱快使用1. 配置WAN12. 配置WAN23. 配置LAN14. 配置DHCP5. DNS设置6. 多线负载7. 基础设置 三、通过交换机拨号1. 交换机设置2. 配置WAN1 一、爱快安装 &#xff08;1&#xff09;下载爱快路由ISO版本 爱快路由系统下载 &#xff08;2&#xff09;IS…

MH-ET LIVE Boards(ATTiny88)实验一---点亮板载灯

MH-ET LIVE Boards(ATTiny88&#xff09;实验一点亮板载灯 在Arduino IDE中添加开发板资源包加入开发板json添加开发板 安装开发板驱动方法一&#xff1a;github下载2.0a4.rar方法二&#xff1a;开发板的package包中自带的2.0a4.rar安装驱动确认安装成功 blink.ino程序测试![在…

Vulnhub靶机:hacksudo1

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;hacksudo1&#xff08;10.0.2.43&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/hac…

Python武器库开发-武器库篇之zip文件暴力破解(五十一)

Python武器库开发-武器库篇之zip文件暴力破解(五十一) Zip文件是一种常用的存档文件格式&#xff0c;用于将多个文件和文件夹压缩成一个单独的文件。它是一种广泛接受和支持的文件格式&#xff0c;几乎所有操作系统和计算机都能够处理Zip文件。Zip文件使用一种压缩算法来减小文…

状态流支持的状态和转换操作类型

状态操作类型 States 可以有不同的操作类型&#xff0c;包含了entry, during, exit, bind, and, on event_name 操作。状态的操作使用以下通用格式的标签表示法分配给操作类型&#xff1a; name/ entry:进入操作 during:在操作期间 exit:退出操作 bind:数据名, 事件名 on even…

使用wda框架实现IOS自动化测试详解

目录 1、weditor元素定位工具 1.1、weditor的安装和使用 2、wda iOS自动化框架 2.1、wda概述 2.2、wda安装 2.3、wda的使用 2.3.1、全局配置 2.3.2、创建客户端 2.3.3、APP相关操作 1、启动APP 2、关闭APP 3、获取APP状态信息 4、获取当前APP的运行信息 2.3.4、设…

Java自救手册

目录 访问地址 访问地址&#xff0c;发现不通&#xff0c;无法访问&#xff1a; 网络不通一般有两种情况&#xff1a; Maven 拿Maven 拿到Maven以后 Maven单独的报红 Git git注意&#xff1a; 目录 访问地址 访问地址&#xff0c;发现不通&#xff0c;无法访问&…

Python中with管理上下文

上下文管理器 上下文管理器本质就是能够支持with操作。 任何实现了 __enter__() 和 __exit__() 方法的对象都可称之为上下文管理器&#xff0c;上下文管理器对象可以使用 with 关键字。显然&#xff0c;文件&#xff08;file&#xff09;对象也实现了上下文管理器协议。 实现…

Canal 结合 SpringBoot 源码梳理

1、canal是什么&#xff0c;可以用来作什么 canal是阿里开源的一个用于监听数据库binlog&#xff0c;从而实现数据同步的工具。 2、安装 我使用的是1.1.5版本&#xff0c;太高的版本需要的jdk版本和mysql的驱动版本会更高&#xff0c;可以根据自己的环境选择。 如果是自己玩的话…

24.云原生之ArgoCD钩子

云原生专栏大纲 文章目录 Argo CD钩子如何定义钩子钩子删除策略 Argo CD钩子 Argo CD 是一个用于部署和管理 Kubernetes 应用程序的工具&#xff0c;它提供了一种声明式的方式来定义和自动化应用程序的部署过程。Argo CD 钩子&#xff08;Hooks&#xff09;是一种机制&#x…

MySQL-- if()函数 简单明了

if 主要有&#xff1a;IF函数嵌套和IF聚合函数 两类&#xff0c;主要是用来根据条件返回不同值。 基本语法为; IF(条件表达式,值1,值2)如果条件表达式为True&#xff0c;返回值1&#xff0c;为False,返回值2.返回值可以是任何值&#xff0c;比如&#xff1a;数值&#xff0c;…