JUC并发编程学习笔记(四)callable接口与辅助类

news2024/12/23 13:51:49

6 Callable&Future 接口

6.1 Callable 接口

创建线程的多种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. Callable接口
  4. 线程池

​ 目前我们学习了有两种创建线程的方法一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口。

现在我们学习的是创建线程的第三种方案—Callable 接口

比较Runnable接口和Callable接口

  • Callable中的call()计算结果,如果无法计算结果,会抛出异常
  • Runnable中的run()使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用该对象的run方法
  • 总的来说:run()没有返回值,不会抛出异常。而call()有返回值,会抛出异常

两个接口代码

//实现Runnable接口
class MyThread1 implements Runnable {
    @Override
    public void run() {

    }
}

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        return 200;
    }
}

具体在主函数中
通过Thread线程创建接口
只有这个可以new Thread(new MyThread1(),"AA").start();
这个不可以new Thread(new MyThread2(),"BB").start();
因为Thread的构造函数中没有Callable接口的参数设置
直接替换不可以,只能用下面这种线程创建方法(找一个类,即和Runnable接口有关系,又和Callable接口有关系

发现Runnable接口有实现类FutureTask(中间对象)
FutureTask的构造函数有Callable参数,通过FutureTask创建线程对象

在这里插入图片描述

6.2 Future 接口

当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法,如下:

  • public boolean cancel(boolean mayInterrupt):用于停止任务。

如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true时才会中断任务。

  • public Object get()抛出 InterruptedException,ExecutionException:

用于获取任务的结果。

如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。

  • public boolean isDone():如果任务完成,则返回 true,否则返回 false

可以看到 Callable 和 Future 做两件事-Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。实际上,future 也可以与 Runnable 一起使用。要创建线程,需要 Runnable。为了获得结果,需要 future。

6.3 FutureTask

FutureTask的构造方法有

  • FutureTask(Callable<> callable) 创建一个FutureTask,一旦运行就执行给定的Callable
  • FutureTask(Runnable runnable,V result)创建一个FutureTask,一旦运行就执行给定的Run你那边了,并安排成功完成时get返回给定的结果

其他常用的代码:
get()获取结果
isDone()判断是否计算结束

FutureTask 具体实现方法可以有两种方式(此处用的泛型参数) 第一种是

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        return 200;
    }
}
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

第二种是

//lam表达式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
    System.out.println(Thread.currentThread().getName()+" come in callable");
    return 1024;
});

所谓的FutureTask是在不影响主任务的同时,开启单线程完成某个特别的任务,之后主线程续上单线程的结果即可(该单线程汇总给主线程只需要一次即可)
如果之后主线程在开启该单线程,可以直接获得结果,因为之前已经执行过一次了

具体完整代码案例

//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
    @Override
    public void run() {

    }
}

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+" come in callable");
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable接口创建线程
        new Thread(new MyThread1(),"AA").start();

        //Callable接口,报错
       // new Thread(new MyThread2(),"BB").start();

        //FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        //lam表达式
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 1024;
        });

        //创建一个线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

//        while(!futureTask2.isDone()) {
//            System.out.println("wait.....");
//        }
        //调用FutureTask的get方法
        System.out.println(futureTask2.get());

        System.out.println(futureTask1.get());

        System.out.println(Thread.currentThread().getName()+" come over");
       }
}

运行截图

在这里插入图片描述

核心原理:(重点)

在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成

  • 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
  • 一旦计算完成,就不能再重新开始或取消计算
  • get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
  • get 只计算一次,因此 get 方法放到最后

6.4 小结(重点)

  • 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future对象获得后台作业的计算结果或者执行状态
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
  • 只计算一次

缺点

Future对于结果的获取不是很友好,只能通过阻塞或者轮询的方式的得到任务的结果。不算真正的异步

7 JUC 三大辅助类

JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为:

  • CountDownLatch: 减少计数
  • CyclicBarrier: 循环栅栏
  • Semaphore: 信号灯

下面我们分别进行详细的介绍和学习

7.1 减少计数 CountDownLatch

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句。

  • CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
  • 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
  • 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行

该类的构造方法为
CountDownLatch(int count)构造一个用给定计数初始化的CountDownLatch在这里插入代码片

两个常用的主要方法
await() 使当前线程在锁存器倒计数至零之前一直在等待,除非线程被中断
countDown()递减锁存器的计数,如果计数达到零,将释放所有等待的线程

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句
具体步骤可以演化为定义一个类,减1操作,并等待到0,为0执行结果

场景: 6 个同学陆续离开教室后值班同学才可以关门。

如果不加 CountDownLatch类,会出现线程混乱执行,同学还未离开教室班长就已经锁门了

public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException {
        
        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    }
}

在这里插入图片描述

具体正确的案例代码

//演示 CountDownLatch
public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException {

        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

                //计数  -1
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }

        //等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    }
}

在这里插入图片描述

7.2 循环栅栏CyclicBarrier

该类是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点,在设计一组固定大小的线程的程序中,这些线程必须互相等待,这个类很有用,因为barrier在释放等待线程后可以重用,所以称为循环barrier

常用的构造方法有
CyclicBarrier(int parties,Runnable barrierAction)创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程操作

常用的方法有
await()在所有的参与者都已经在此barrier上调用await方法之前一直等待

通过具体案例
集齐7颗龙珠就可以召唤神龙
完整代码

//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {

    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args) {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->{
                    System.out.println("*****集齐7颗龙珠就可以召唤神龙");
                });

        //集齐七颗龙珠过程
        for (int i = 1; i <=7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
                    //等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述

总结:
CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作

7.3 信号灯Semaphore

一个计数信号量,从概念上将,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后在获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动
具体常用的构造方法有
Semaphore(int permits)创建具有给定的许可数和非公平的公平设置的Semapore

具体常用的方法有:
acquire()从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
release()释放一个许可,将其返回给信号量

设置许可数量Semaphore semaphore = new Semaphore(3);
一般acquire()都会抛出异常,releasefinally中执行

通过具体案例
6辆汽车,停3个车位
完整代码:

//6辆汽车,停3个车位
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                try {
                    //抢占
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName()+" 抢到了车位");

                    //设置随机停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                    System.out.println(Thread.currentThread().getName()+" ------离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

运行效果:

在这里插入图片描述

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

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

相关文章

某电视 频道headers参数x-itouchtv-ca-signature逆向

本文仅供参考学习&#xff0c;如有侵权可联系本人 某电视(荔枝平台)平台逆向 目标链接 aHR0cHM6Ly93d3cuZ2R0di5jbi9jaGFubmVsRGV0YWlsLzI0Ng接口链接&#xff1a; aHR0cHM6Ly9nZHR2LWFwaS5nZHR2LmNuL2FwaS9jaGFubmVsL3YxL25ld3M/cGFnZVNpemU9NDAmY2hhbm5lbElkPTI0NiZjdXJy…

纯注解驱动SSM整合

纯注解驱动SSM整合 Jdbc.java package com.itheima.confing;import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class J…

思购趣拼秒杀模式的逻辑与模式特点

读&#xff1a;小镇上来了一个陌生的年轻人&#xff0c;年轻人交了一百块定金给旅馆的老板去楼上挑房间&#xff0c;旅馆老板拿着这一百块钱还给了卖猪肉的&#xff0c;卖猪肉的把钱还给了卖饲料的&#xff0c;卖饲料的把钱还了厂商&#xff0c;厂商拿着钱还给了按摩店的小姐&a…

一文说清MySQL索引数据结构

前言 接上篇说到&#xff0c;小A匆匆忙忙的赶回宿舍&#xff0c;因为晚上他要给女神整理讲解MySQL中索引数据结构资料。一边整理一边忍住不笑了起来&#xff0c;等小美看到这篇文章不得爱上自己。当上小美男朋友&#xff0c;从此踏上人生巅峰不是梦&#xff08;该考虑一下孩子…

C#,图像二值化(12)——基于谷底最小值的全局阈值算法与源代码

1、基于谷底最小值的阈值 这个方法适用于通过有限的迭代次数&#xff0c;平滑后能得到双峰的图像&#xff0c;让双峰的谷底成为阈值。当执行完基于谷底最小值的阈值操作的时候&#xff0c;改变了直方图信息&#xff0c;使之成为处理过后的直方图信息&#xff0c;这时候显示Doc…

LeetCodeday05

面试题 02.07. 链表相交 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; public class Leetcode0207 {public static void main(…

ChatGPT背后的模型

文章目录1.RLHF方法2.ChatGPT中的RLHF方法2.1 微调模型GPT-32.2 训练奖励模型2.3 利用强化学习进一步微调语言模型3.效果4.面临挑战5.参考InstructGPT语言模型&#xff0c;是一个比 GPT-3 更善于遵循用户意图&#xff0c;同时使用通过我们的对齐研究开发的技术使它们更真实、毒…

应急/linux 挂D盾扫描方法

0x00 linux 挂D盾扫描方法 这个方法可以解决Linux下无法使用D盾查杀webshell的困扰 利用ssh将Linux文件系统挂在到win上面&#xff0c;然后扫描就好了 0x01 环境安装 安装D盾 D盾都用过吧&#xff0c;应急经常用来扫一下是否存在 Webshell D盾下载地址 D盾防火墙 (d99ne…

python 性能优化

文章目录性能测试运行速度内存消耗并行加速分布式并行多线程多进程框架即时编译njitcase1 计算熵case2 找到最大概率类别case3 计算两两准确率GPU使用工具关于程序优化的第一个准则是“不要优化”&#xff0c;第二个准则是“不要优化那些无关紧要的部分”。性能测试 性能测试是…

4种FPGA时钟分频 【附源码】:1.偶数分频;2.奇数分频(占空比50%);3.奇数分频(任意无占空比);4.小数分频;

题目来源于牛客网&#xff0c;完整工程源码&#xff1a;https://github.com/ningbo99128/verilog 目录 VL37 偶数分频 VL40 奇数分频&#xff08;占空比50%&#xff09; VL42 奇数分频&#xff08;任意无占空比&#xff09; VL41 任意小数分频 VL37 偶数分频 题目介绍 请…

https的相关知识,为什么https更加安全,为什么要对称与非对称加密,非对称加密的算法

目录 https相比于http更加安全&#xff0c;三个优势&#xff1a; 下面是一些必须知道的问题 1.对称加密与非对称加密&#xff1a; 2.对称加密的密钥SK如何产生和传输&#xff1a; 3.https有两套非对称加密 4.https的哈希一共用于两个地方 5.https的整个流程 https相比于ht…

ElasticSearch数据实时性原理分析与持久化

问题复现 现在有这么一种业务场景&#xff0c;需要将海量的数据通过Hive进行数据清洗并统计&#xff0c;最后落库到ES中&#xff0c;因为需要支持大数据量的分词&#xff0c;模糊搜索&#xff0c;所以考虑用ES而不直接放到Mysql中&#xff0c;前端需要直接对数据进行交互&…

Redis的自增也能实现滑动窗口限流?

文章目录限流核心原理以及代码基于Spring切面实现的注解版本限流是大家开发之路上一定会遇到的需求。比如&#xff1a;限制一定时间内&#xff0c;接口请求请求频率&#xff1b;一定时间内用户发言、评论次数等等&#xff0c;类似于滑动窗口算法。这里分享一份拿来即用的代码&a…

buildroot构建hisi平台根文件系统和工具链

buildroot构建hisi平台根文件系统和工具链 前面使用了arm-hisiv300-linux 工具链来作为Buildroot的外部工具链进行编译&#xff0c;然后遇到了很多编译问题。 https://blog.csdn.net/duapple/article/details/128516133?spm1001.2014.3001.5501 这里不使用hisi的工具链&…

Seata简介

小结&#xff1a; nacos 【name server】&#xff1a;注册中心&#xff0c;解决服务的注册与发现 nacos【config】&#xff1a;配置中心&#xff0c;微服务配置文件的中心化管理&#xff0c;同时配置信息的动态刷新 Ribbon&#xff1a;客户端负载均衡器&#xff0c;解决微服务集…

C++之引用类型,深浅拷贝构造

引用类型&#xff1a;给内存段取别名。 int m 10; //引用&#xff0c;给内存段取别名&#xff0c;所以需要给他一段内存段&#xff0c;而不只是声明。 int& n m;//不是赋值的意思&#xff0c;是别名的意思 想要在被调函数中修改主调函数中定义的变量的值时&#xff0c;…

小程序用户头像昵称获取规则调整与之对应调式策略、新API接口的bug

目录 调整时间 调整背景 调整说明 开发者与之对应的debug策略 1.button里面包含一个image&#xff0c;这种包含关系 2.然后我们可以看到官方给出的是用button组件中的open-type属性并且给到了一个chooseAvatar值&#xff01; 3.我们会发现光放给我们了一个“配置好的”命…

CSAPP Cache Lab

CSAPP Cache Lab 本实验将帮助您了解缓存存储器对 C 语言性能的影响程式。实验室由两部分组成。 在第一部分中&#xff0c;您将编写一个小的 C 程序&#xff08;大约 200-300 行&#xff09;模拟高速缓存的行为。 在第二部分中&#xff0c;您将优化一个小型矩阵转置函数&#…

NoMachine出现 The session negotiation failed的解决方案及踩坑总结

问题情况&#xff1a;我A电脑输入用户名和密码可以远程B电脑&#xff0c;B电脑输入用户名密码就是登录不上A电脑。 B电脑上密码是用的账户密码&#xff08;就是图标是一把钥匙的那个&#xff09;。 A电脑上的密码是用的PIN密码&#xff08;Win11系统推荐的那个&#xff09;。 通…

ArcGIS基础实验操作100例--实验37线要素生成规则或随机采样点

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验37 线要素生成规则或随机采样点 目录 一、实验背景 二、实验数据 三、实验步骤 &…