Callable接口_JUC的常见类_多线程环境使用ArrayList

news2024/11/23 13:03:49

 

目录

1.Callable接口

相关面试题

2.ReentrantLock

相关面试题

3.信号量Semaphore

4.CountDownLatch

5.多线程环境使用ArrayList

热加载


1.Callable接口

Callable是一个接口,把线程封装了一个"返回值",方便程序员借助多线程的方式计算结果.

类似于Runnable,是用来描述一个任务,区别就是Runnable描述的任务没有返回值,而Callable描述的任务有返回值

看这个例子

创建线程计算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本

public class Test {
    static class Result {
        public int sum = 0;
        public Object lock = new Object();
    }
    public static void main(String[] args) throws InterruptedException {
        Result result = new Result();
        Thread t = new Thread() {
            @Override
            public void run() {
                int sum = 0;
                for (int i = 1; i <= 1000; i++) {
                    sum += i;
                }
                synchronized (result.lock) {
                    result.sum = sum;
                    result.lock.notify();
                }
            }
        };
        t.start();
        synchronized (result.lock) {
            while (result.sum == 0) {
                result.lock.wait();
            }
            System.out.println(result.sum);
        }
    }

}

使用Runnable来描述任务,没有返回值,这里使用了一个辅助类result,还需要使用一些加锁和wait,notify操作,比较复杂易出错

我们使用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;
            }
        };
创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的 call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结
果. 

        Integer result = futureTask.get();
        System.out.println(result);
    }

 call方法就相当于Runnable的run方法,只不过call方法会返回一个泛型返回值,run返回void

创建好任务后,需要一个线程来执行,但是这里不能把callable直接传入Thread的构造方法中,需要给它套上一个辅助类

FutureTask实现了RunnableFuture<V>接口,RunnableFuture<V>接口继承了Runnable, Future<V>

public class FutureTask<V> implements RunnableFuture<V>{
        ...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

FutureTask既是Runnable对象,也是Future对象。Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作,get方法会阻塞,直到任务返回结果

 

 get方法就是获取结果.get会发生阻塞,直到callable执行完毕,get才阻塞完成,获取到结果

Callable也是创建线程的一种方式

相关面试题

介绍下 Callable 是什么

Callable是一个接口,相当于把线程封装了一个"返回值",方便程序员借助多线程方式计算结果

Callable和Runnable相对,都描述一个任务,Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务

Callable通常搭配FutureTask使用,FutureTask用来保存Callable的返回结果,因为Callable往往是在另一个线程中执行的,不知道具体执行完的时间,FutureTask就是负责等待结果出来并保存的

JUC的常见类:

2.ReentrantLock

ReentrantLock是标准库提供的另一种锁,是可重入锁

synchronized是基于代码块的方式加锁解锁的

ReentrantLock比较传统,使用了lock和unlock方式加锁解锁

ReentrantLock 的用法:

lock(): 加锁, 如果获取不到锁就死等.

trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.

unlock(): 解锁

public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        
        reentrantLock.unlock();
    }

 在lock和unlock之间就是加锁的部分,但是这样写有可能unlock执行不到,加锁的代码块中间如果存在return或者异常都可能导致unlock不能顺利进行

 或者在return之前加上unlock,但是如果这种条件语句很多,那么就会很麻烦,此时用finally来解决

上述是ReentrantLock的劣势,但是它也是有优势的

1.ReentrantLock提供了公平锁版本的实现

2.synchronized提供的加锁操作是"死等",只要获取不到锁就一直等待,ReentrantLock提供了更灵活的等待方式:tryLock

无参数版本:能加锁就加,加不上锁就放弃

有参数版本:指定了超时时间,加不上锁就等待,如果超时了还没获取到锁就放弃

3.ReentrantLock提供了一个更强大,更方便的等待通知机制.

synchronized搭配的是waitnotify,notify的时候是随机唤醒一个wait的线程

ReentrantLock搭配一个Condition类,进行唤醒的时候可以唤醒指定的线程

虽然ReentrantLock有一定的优势,但是实际开发常用的是synchronized

相关面试题

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

synchronized使用时不需要手动释放锁,ReentrantLock 使用时需要手动释放. 使用起来更 灵活

synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式

synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的 线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程

3.信号量Semaphore

操作系统中的信号量和此处的信号量是相同的,只不过此处的信号量是Java把操作系统原生的信号量封装了一下

信号量本质是一个"计数器",用于描述可用资源的个数

就相当于停车场的入口的显示牌,有多少个车位可用!如果车位满了,那么牌子就是显示可用为0.那么再想要停车,就只能换地方,或者等待.

围绕这个计数器有两个操作:

P操作:申请一个可用资源,计数器就-1

V操作:释放一个可用资源,计数器就+1

P操作如果要是计数器为0了,继续P操作,就会阻塞等待

P操作使用acquire申请

V操作使用release释放

考虑一个计数初始值为1的信号量,针对这个值,只能有1和0两个取值,不会是负值.

如果执行P操作,1->0

如果执行V操作,0->1

如果进行一次P操作了,再进行一次P操作,就会阻塞等待

那么根据这个特性我们很容易联想到锁(锁可以视为计数器为1的信号量,有两个取值,称为二元信号量)

锁是信号量的一种特殊情况,信号量是锁的一般表达

实际开发中信号量也会用到,比如说,图书馆中有某本书有20本,那么就可以用初始值为20的信号量.借书就P操作,还书就V操作.如果书为0了,再P操作就会阻塞等待!

public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(3);
        semaphore.acquire();
        System.out.println("第一次P操作");

        semaphore.acquire();
        System.out.println("第二次P操作");

        semaphore.acquire();
        System.out.println("第三次P操作");

        semaphore.acquire();
        System.out.println("第四次P操作");
    }

 申请了3个可用资源,当申请第四个就会阻塞等待,等到别的线程调用release释放可用资源,才能停止阻塞

 无参版本是申请一个资源,带参数版本可以指定申请的资源数

代码中也可以使用Semaphore来实现类似于锁的功能,保证线程安全 

4.CountDownLatch

同时等待 N 个任务执行结束

类似于跑步比赛,多个选手都就位,哨声响才同时出发,所有选手都通过终点,才公布成绩

跑步比赛开始时间都是相同的,但是结束时间是不明确的,为了衡量这个时间引入了CountDownLatch,主要提供了两个方法

1.await 方法,主线程来调用该方法,很多"a"为前缀的术语都表示异步.

同步和异步是相对的,同步:发送请求方自己主动等待响应结果

异步:发送请求方请求完就不管了,等有结果了,对方主动将结果推送出来

2.countDown表示冲过了终点线.

CountDown在构造的时候,指定一个计数器(选手的个数).例如,指定了四个选手进行比赛,初始情况下,调用await,就会阻塞,每个选手冲过终点后都会调用countDown方法,前三次调用countDown,await没有任何影响,第四次调用countDown,await就会被唤醒,返回,解除阻塞,此时认为整个比赛结束!

在开发中CountDownLatch也有很多应用场景,比如下载一个大的文件.使用多线程下载,把一个大的文件切分成多个小块儿的文件,安排多个线程分别下载,多线程下载,不是充分利用了多核CPU,而是充分利用了带宽(下载是IO操作,与CPU关系不大),此处就可以用CountDownLatch来区分是不是整体都下载完了

5.多线程环境使用ArrayList

Java标准库中的大部分集合都是"线程不安全"的,多个线程使用同一个集合类对象,很可能会出现问题

Vector, Stack, HashTable, 这几个集合是线程安全的,关键方法带有synchronized

多线程环境使用ArrayList:

1.自己加锁,使用synchronized或者ReentrantLock

2.Collections.synchronizedList,可以使用这个方法把集合类套一层,这里会提供一些Array List相关的方法,同时是带锁的

3.CopyOnWriteArrayList

简称为"COW",即"写时拷贝",如果针对这个ArrayList进行读操作,不做任何额外的工作,如果进行写操作,则拷贝一份新的ArrayList,针对新的进行修改,修改过程中如果有读操作,就继续读旧的这份数据,当修改完毕了,使用新的替换旧的(本质上是一个引用之间的赋值,是原子的)

这种方案的优势很明显,就是不用加锁了,缺点也很明显,就是这个Array List不能太大了,只适用于这种数组比较小的情况下

热加载

我们知道服务器程序的配置与维护是需要修改配置文件的,修改配置后就需要重启才能生效,但是重启操作成本比较高,重启过程中服务就中断了,用户发送的请求就没有响应了,产生的后果是很不好的,因此,很多服务器提供了"热加载"(reload)这样的功能

"热加载"功能可以不重启服务器,实现服务器程序的配置的更新,热加载的实现就是用写时拷贝的思想,新的配置放到新的对象中,加载过程里,请求仍然基于旧的配置进行工作,当新的对象加载完毕,使用新的对象代替旧的对象(替换完成之后,旧的对象就可以释放了)

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

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

相关文章

五个了解自己天赋优势的分析工具(三)DISC性格测评

DISC性格测评 DISC系统源于1928年&#xff0c;马斯顿在他的著作《正常人的情绪》(The Emotion of Normal People)中公布了他所发现及发展的性格理论。 该书首次尝试将心理学从纯粹的临床应用向外延伸应用到一般人身上。人有四种基本的性向因子&#xff0c;即Dominance -支配&…

Duet 安装教程

Duet 安装教程1. Duet 概述2. Duet 安装教程2.1 PC 端下载安装Duet2.2 iPad 下载安装 Duet3. 将iPad作为Windows电脑副屏的几种方法结束语1. Duet 概述 Duet 是一款能将iPad或iPhone 变成 Mac 或者 PC 的显示屏的软件&#xff1b; 通过线材连接两台不同的设备&#xff0c;Duet…

抖音聊天”上线,字节最后的社交梦?

转眼间时间来到2023年&#xff0c;距离中国接入国际互联网&#xff08;即中国互联网起点&#xff09;已过40年。回顾中国的互联网江湖&#xff0c;先有BAT三足鼎立&#xff0c;后有TMD后浪居上。所谓BAT&#xff0c;即互联网时代领头羊百度、阿里巴巴和腾讯&#xff0c;而TMD则…

【Java入门】常量和变量

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;Java入门 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又一…

HTTP/HTTPS协议介绍

数据来源 HTTP 01 什么是HTTP 超文本传输协议(HyperTextTransferProtocol缩写&#xff1a;HTTP&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议。 HTP( Hyper Text Transfer Protocol超京本传输协议) 是一个基于请求与响应 无状态的&#xff0c;应用层…

mysql快速生成100W条测试数据(7)虚拟网站、IP地址并存入mysql数据库

这是之前的文章里面包含一些以前的一些操作流程可以进行参考学习 更加详细操作步骤在第一篇文章里面 mysql快速生成100W条测试数据&#xff08;1&#xff09;&#xff1a;游戏人物数据 mysql快速生成100W条测试数据&#xff08;2&#xff09;公司员工信息 mysql快速生成100W条测…

《Unity Shader 入门精要》第2章 渲染流水线

第2章 渲染流水线 2.1 什么是渲染流水线 渲染流水线的工作在于由一个三维场景出发&#xff0c;生成一张二维图像。换句话说&#xff0c;计算机需要从一系列的顶点数据、纹理等信息出发&#xff0c;把这些信息最终转换成一张肉眼可见的图像&#xff0c;而这个过程通常由CPU与G…

静态链接过程分析

前期准备这边使用《程序员的自我修养》中的例子//a.cpp extern int shared;void swap(int* a, int *b);int main(){int a 100;swap(&a, &shared); }//b.cpp int shared 1;void swap(int* a, int* b){*a ^ *b ^ *a ^ *b; }通过gcc -c 命令编译出相对应的.o文件&#x…

五,Spring Bean生命周期

1 Spring Bean的生命周期&#xff08;概念重点&#xff09; 对象的生命周期&#xff1a;对象从生到死的过程。 Spring工厂中Bean的生命周期并不像想象的那么简单&#xff0c;Spring对工厂中的Bean的生命周期进行了细致的划分&#xff0c;并允许开发者通过编码或配置的方式定制…

Ubuntu18 sqlyog配置mysql5.7远程连接

mysql 配置远程连接 1. mysql安装和配置 sudo apt-get install mysql-server-5.7 systemctl status mysql service mysql status修改mysql的配置文件&#xff1a; sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf以下为mysqld.cnf文件主要内容&#xff0c;这里的skip-grant-ta…

基于51单片机的pm2.5空气质量监测仪仿真设计

51单片机pm2.5监测仪仿真设计( proteus仿真程序报告讲解视频&#xff09; 仿真图proteus 7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0032 51单片机pm2.5监测仪仿真设计主要功能&#xff1a;讲解演示视频仿真程序设计…

代码整洁提升方案

验-言 公共方法都要做参数的校验&#xff0c;参数校验不通过明确抛出异常或对应响应码&#xff1a; Java Bean验证已经是一个很古老的技术了&#xff0c; 会避免我们很多问题&#xff1b; 在接口中也明确使用验证注解修饰参数和返回值&#xff0c; 作为一种协议要求调用方按…

win11 arm 系统安装安卓子系统

一般的x86电脑如果安装android子系统&#xff0c;运行安卓子系统&#xff0c;由于要将android arm代码转译为x86代码&#xff0c;所以效率不一定高&#xff0c;但是如果电脑是arm架构的&#xff0c;通过安卓子系统运行android的程序执行效率就会 高不少&#xff0c;本文参考,都…

JVM面试题

Java内存区域 说一下 JVM 的主要组成部分及其作用&#xff1f; JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、Execution engine(执行引擎)&#xff1b;两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 Class loader…

Es6的Promise

Promise是异步编程的一种解决方案。简单来说Promise就是一个用来存储数据的对象&#xff0c;它有着一套特殊的存取数据的方式。可以解决异步回调函数/回调地狱问题。创建Promise1.创建Promise时需要一个 回调函数 作为参数这个回调函数会在Promise时 自动调用2.调用回调函数时&…

基于matlab的指纹图像处理、脊线增强、脊线分割、脊线细化、细节点检测和细节点验证

需求分析对于指纹的特征提取包含几个步骤&#xff0c;脊线增强、脊线分割、脊线细化、细节点检测和细节点验证&#xff0c;本次大作业需要针对已经增强的指纹图片进行后续几个步骤&#xff0c;通过多种形态学算法进行分割、细化、细化后处理&#xff0c;找到其中的端点和分叉点…

elasticsearch基础2——es配置文件、jvm配置文件详解

文章目录一、配置文件详解1.1 elasticsearch.yml文件1.1. 1 基础参数1.1.1.1 自定义数据/日志目录1.1.1.2 锁定物理内存1.1.1.3 跨域设置1.1.1.4 其他参数1.1.2 集群类1.1.3 分片类1.1.4 IP绑定类1.1.5 端口类1.1.6 交互类1.1.5 Xpcak安全认证1.1.5.1 xpack内置用户1.1.5.2 xpa…

LabVIEW使用VI脚本向VI添加对象

LabVIEW使用VI脚本向VI添加对象可使用VI脚本向前面板和程序框图添加对象。该教程以向程序框图添加对象为例。按照下列步骤&#xff0c;通过VI脚本向VI添加对象。创建VI前&#xff0c;需先了解VI脚本的基本内容。必须启用VI脚本&#xff0c;才能显示VI脚本选板&#xff0c;使用相…

aws beanstalk 理解和使用eb工作线程环境

参考资料 beanstalk 工作线程环境beanstalk 工作线程环境管理https://catalog.us-east-1.prod.workshops.aws/workshops/b317e4f5-cb38-4587-afb1-2f75be25b2c0/en-US/03-provisionresources 理解 beanstalk 工作线程环境 https://docs.amazonaws.cn/elasticbeanstalk/latest…

【Java基础】-【线程】

文章目录创建线程的方式Thread类的常用方法run()和start()有什么区别&#xff1f;线程是否可以重复启动&#xff0c;有什么后果&#xff1f;线程的生命周期实现线程同步Java多线程之间的通信方式sleep()和wait()的区别notify()、notifyAll()的区别如何实现子线程先执行&#xf…