多线程适用接口及常见类

news2025/1/20 16:59:09

 日升时奋斗,日落时自省

目录

1、Callable接口

1.1、Callable方式

1.2、非Callable方式

2、JUC(java.util.concurrent)的常见类

2.1、ReentrantLock

2.2、信号量Semaphore

2.3、CountDownLatch

3、线程安全的集合类

3.1、多线程使用ArrayList

3.2、多线程使用哈希表

3.2.1、Hashtable

3.2.2、ConcurrentHashMap

1、Callable接口

Callable接口类似于Runnable一样(稍有区别)

Runable 用来描述一个任务,描述的任务没有返回值

Callable 也是用来描述一个任务, 描述的任务有返回值

Callable适用条件也就一目了然了,如果是一个线程单独计算某个值出来的话,Callable就比较合适。

1.1、Callable方式

那我们简单看一下 Callable接口使用

Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //这里就是简单写一个加加的例子
                int sum=0;
                for(int i=0; i<1000;i++){
                    sum+=i;
                }
                //主要是说明Callable可以有返回值
                return sum;   //这里哈
            }
        };

 在创建一个类的时候会带有call方法,call方法就相当于是Runnable的run方法,run方法返回值是void,此时的call返回值是Integer,这里泛型的参数是Integer,所以返回值由 泛型参数决定。

既然类似Runable就 在创建线程那也就是不能直接调用的了。

所以走特殊途径这里就涉及到一个辅助类(FutureTask)

FutureTask刚刚出现,不容易记住与接受

举例理解:在一家饭店里,创建的callable对象就是一碗饭, 但是一家店里不是一个人来吃饭,所以这碗饭是谁的就需要证明了,FutureTask就是单子(小票)证明它是那为客人点的,有单子了,这不就顺理成章了,直接开锅做饭(放到线程里就知道对象是谁了,可以运行)

那FutureTask创建的辅助类已经接收了返回值,如何拿出来???

FutureTask内部方法get()就可以取出来刚刚的sum

 这里的get是要抛两个异常的ExecutionException, InterruptedException一个是执行异常,另一个是阻塞异常,所以get是获取结果的,get也会发生阻塞,直到callable执行完毕,get才阻塞完成,才会获取结果

//java标准库中提供了专门的Callable接口来解决返回值的问题
    /*
    * 代码写起来也比Thread更加理解
    * */
    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=0; i<1000;i++){
                    sum+=i;
                }
                return sum;
            }
        };
        //但是这里的Callable 是不能直接装在Thread里面的
        /*
        * 需要一点点的东西 , 通过一个媒介就行
        * */
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();
        int result=futureTask.get();  //这里的get需要抛出两个异常
        System.out.println(result);
    }

这里把代码附上,这样写出来是不是比较好看,也很清晰,本身代码量少。就是接触两个新的东西一个Callable和FutureTask辅助类 理解后就不会看着很难了;相比Thread,来实现返回值也是可以的但会比现在的麻烦一点,下面就介绍这种方法,多一种方法,多一种理解。

1.2、非Callable方式

思路:

(1)创建一个自定义类 定义一个计算和的变量, 一个Object对象

(2)main方法中创建一个线程然后run方法中写计算和

(3)计算和写完后,仍在线程内写一个赋值与唤醒(唤醒谁呢,看(4))

(4)线程开始执行后,此时写wait等待,等待run方法执行完了,唤醒我,再打印当前计算和值

class  Result{
    public int sum=0;    // 防止局部作用域的影响
    public  Object lock=new Object();  //进行判定的
}
public class NoCallable {
    //Thread是没有返回值的,但是线程也可能会需要用到,在Thread中也是可以做到的
    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=0;i<100;i++){
                    sum+=i;
                }
                synchronized (result.lock){  //如果当前对象没有锁  这里就加锁当前部分
                    result.sum=sum;  //这里是要采取赋值的,为什么因为当前sum是在run方法内定义的,拿不出去,只能赋值给自定义类的变量
                    result.lock.notify(); //唤醒是为了,让主线程在结束位置等等,要不还没有执行,就结束,看不到任何结果
                }
            }
        };
        t.start();
        synchronized (result.lock){
            //结果值不能为0 ,为0了等于没有加 出问题了,这里谨慎一点
            while (result.sum==0){
                //在线程开始执行后,等待run方法执行结束 唤醒我才能往后走,进行打印
                result.lock.wait();
            }
              //如果不在最后这是wait就会导致主线程提前结束,可能得不到任何结果
            System.out.println(result.sum);
        }
    }
}

附代码上有注释,这里就不做过多解释了

2、JUC(java.util.concurrent)的常见类

2.1、ReentrantLock

我们现在以提到锁基本就是synchronized,ReentrantLock同样是一种锁;

reentrant(翻译:可重入)感觉这个新单词不好记,那英语的办法记 entry是这个词的词根,加了一个re的前缀,和一个后缀ant就成了,类就是在该单词后面加了Lock(翻译:锁)

那既然都是锁,就有了可比性

区别:

相同点:synchronized和ReentrantLock两把锁都是可重入的(相同点比较少)

不同点:

(1)synchronized是直接基于代码块方式来加锁解锁的,ReentrantLock比较传统,使用lock方法和unlock方法加锁解锁

public static void main(String[] args) {
        ReentrantLock now=new ReentrantLock();   //创建类
        //传统  方法加锁
        now.lock();
        //传统  方法解锁
        now.unlock();
    }

(2)synchronized是一个关键字是有JVM内部实现,ReentrantLock是java标准库里的一个类,是JVM外部实现的。

(3)synchronized使用时不需要手动释放锁,ReentrantLock是手动加锁和释放锁,使用更灵活,但是也容易遗漏unlock

为什么这么说??? 因为不是所有情况都是代码结束了才返回

public static void main(String[] args) {
        ReentrantLock now=new ReentrantLock();
        //传统  方法加锁
        now.lock();
        /*
        * 如果 在if中返回了不是就没有经过 unlock操作 
        * */
        if(true){
            return ;
        }
        //传统  方法解锁
        now.unlock();
    }

问题出现了,如何解决? 在if中return前面加一个解锁,但是if操作多了怎么办一个一个加嘛,总会有漏的时候;此时使用另一个办法就是直接在最后加一个finally程序执行结束必定会执行解锁

public static void main(String[] args) {
        //ReentrantLock 是一个可重入锁   reentrant就是可重入的意思
        ReentrantLock lock=new ReentrantLock();
        lock.lock();   //这就是简单的上锁
        try {
            /*
             * 但是有一个缺陷就是 解锁可能会被忘记  如果添加一个 if 语句就会导致当前 程序提前结束
             * */
            if (true) {
                return;
            }
            /*
             * 为了避免这种情况 直接用finally 来处理 因为 finally 是一定会执行的
             * */
        }finally {
            lock.unlock();   //解锁   是比较灵活的
        }
    }

(4)synchronized在申请锁失败的时候选择方式死等,ReentrantLock可以通过trylock方法进行一段时间等待后如果没有解锁,就放弃锁;没有设置等待时间就直接放弃锁。(trylock会返回一个布尔类型的值)

问题:那放弃锁,不就是没有加锁,那还要解锁吗?回答:当然不用

处理方式:在finally里面加一个if语句进行判定就行 如果加锁 就解锁, 没有加锁就不管

public static void main(String[] args) {
         ReentrantLock lock=new ReentrantLock();
         boolean flag=lock.tryLock();   //如果没有设定时间的话就是立刻放弃
        /*
        * 如果设置时间 时间到了,就会放弃这个锁 提高效率
        * */
        /*
        * 疑惑就是 不知道是否上锁, 该方法返回值是 boolean 用一个变量来接收if判断
        * */
        try{

        } finally {
            if(flag){
                //加锁  就可以解锁
            }else{
                //没有加锁  不用解锁
            }
        }
    }

(5)synchronized是非公平锁,ReentrantLock默认是非公平锁,可以通过构造方法传入一个true变成公平锁(可以在Idea中自信点开看 一个参数的构造方法)

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

使用选择                   

注:上面的区别就是使用的偏向 (这里只是大体总结一下)

<1> 锁竞争不激烈的时候,使用synchronized效率更高,自动是释放更方便

<2>锁竞争激烈的时候,使用ReentrantLock可以控制时间,不需要一直死等,看自己的检测情况

<3>想用公平锁就是ReentrantLock

2.2、信号量Semaphore

友情提醒:操作系统信号量和java中的信号量不是一个东西

此处的信号量用来表示“可用资源的个数”。本质上就是一个计数器

什么是可用资源个数??? 

举例解释:就以酒店来说,有人住店房间数就会-1, 住店者离开就会空房数+1 ,空房数就是可用资源个数。

PV操作:

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

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

P操作如果造成计数器等于0了,继续P操作就会出现阻塞(就像顾客来住店没有房间了,只能等就是阻塞)

信号量可以延伸

计数初始值为1 的信号量 那就只有1和0两种结果(信号量没有负值)

执行一次P操作 1->0

执行一个V操作 0->1

如果已经进行一次P操作了,继续进行P操作就会阻塞:这里就像是一个线程加一把锁,另一个线程还想加这把锁就阻塞了

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

这里就不难看出Semaphore能实现类似于锁的效果,来保证线程安全

这里附一个代码演示使用刚刚申请(acquire)方法和 释放(release)这两个方法

// Semaphore 就像是一个裁判一样 能够限制任务做几个  信号量 java中的表示
    public static void main(String[] args) {
        //限制了当前只能有四个信号量  所以即便是多线程执行 一次也只能执行四个任务,其他线程得等着
        Semaphore semaphore=new Semaphore(4);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (this){
                        semaphore.acquire();   //该方法就是  申请一个位置
                        System.out.println( "申请资源");
                    }
                    Thread.sleep(500);
                    semaphore.release();
                    System.out.println("释放资源");   //释放一个位置

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        for(int i=0; i<20;i++){
            //这里创建了20个线程  不会一次全部执行完的
            Thread t=new Thread(runnable);
            t.start();
        }
    }

2.3、CountDownLatch

作用同时等待N个任务结束

举例解释:以比赛为例:一个田径比赛 一个比赛10个人,裁判会等每个人会来才会结束比赛(就是这个意思)

(1)CountDownLatch实例,初识10个值

(2)每个线程(人)执行任务完成,latch.countDown()。该方法内部计数减减

(3)使用latch.await();阻塞等待所有任务执行完毕,阻塞等待才会结束(这里在详细一点,等第10个人执行了latch.countDown()方法 await就结束了)

//能同时计时多个任务
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch lacth=new CountDownLatch(10);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((long) Math.random()*1000);
                    lacth.countDown();  //内部计数器减减
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        for (int i=0;i<10;i++){
            //是个线程都执行
            new Thread(runnable).start();
        }
        //十个人都会来才算是结束
        lacth.await();
        System.out.println("比赛结束");
    }

虽然这里说是所有线程都执行结束,当然也可以不是所有的,根据自己的情况设计的等待个数。

在实际中有很多地方可以用得到,例如视频文件下载,多个线程分块下载提升速度,多线程下载不是充分利用了多核CPU,而是充分李利用了IO(下载是IO操作),所以现在使用CountDownLatch多个线程执行结束也就下载完成,

3、线程安全的集合类

java标准库大部分集合类都是“线程不安全”的,多线程使用同一个集合类对象,很可能不安全

安全的有哪些:Vector,Stack ,HashTable这几个类线程安全(内部带有synchronized),但是在线程上不常用

3.1、多线程使用ArrayList

如何安全使用 ,ArrayList本身是不安全的

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

(2)使用集合类套一层锁,Collects.synchronizedList 这里会提供一些ArrayList相关的方法,同时是关键操作带锁的(加的是synchronized锁),使用方式大同小异。

 List<Integer> list=Collections.synchronizedList(new ArrayList<>());

(3)CopyOnWriteArrayList(拷贝用的)简称COW 也叫做“写时拷贝” 如果针对这个ArrayLIst进行操作,则拷贝一份新的ArrayList,针对新的容器进行修改,修改过程中如果有读操作,就继续读旧的容器的数据,当修改完毕了,使用新容器替换旧容器(这样比较保险,单方面只拷贝的话,中途有数据写入时就不好处理了(脏读问题))

优点:不需要加锁,执行速度快,在多读少写的场景下

缺点:占内存多,开辟数组不大。

总述:只是适用于数组比较小的情况下

方法不一定常用,可以了解了解思维:

举例解释:以服务器为例子,服务器程序的配置 、维护(配置文件很常听),服务器中也存在有想要的功能和不想要的功能(针对不同人),所以就会有服务器修改配置文件,修改后一般服务器都是要关了再重开的。(数据库mysql为例 配置文件 my.ini)

但是重启操作可以能成本比较高,这里就不拿本地电脑mysql举例了,因为mysql自己电脑上就算是服务器关了再开也不会对你产生什么影响,此处假设一个服务区重启需要花5分钟时间(大型服务器),现在服务器接收多了一般也不会采用一台,分布式解决问题,10台这样的服务器,重启就会消耗50分钟时间,(不能同时重启这10个服务器,如果同时重启,就没有办法在接收响应了,带来的损失也会很大)。

这里其实就明白服务器重启是比较不好用的,服务器也有提供“热加载”这样的功能,通过这样的功能就可以不重启,实现配置更新,就代表服务器也不需要担心服务器拥挤,“热加载”就可以“写时拷贝”的思路。

写时拷贝总述:新的配置放在新的对象中,加载过程中,请求仍然基于旧的配置进行工作,当新的对象加载完毕,新的配置可以代替旧的了,旧的也就释放了

3.2、多线程使用哈希表

HashMap本身是不具有线程安全的

哈希表很也好用,便捷,在多线程环境下也可以使用,需要其他类Hashtable  、ConcurrentHashMap这两个类来调用哈希表,下面分开说这两个类

3.2.1、Hashtable

Hashtable是线程安全的 ,给关键方法 ,加了synchronized

 这里就看两个比较常见的方法,当然Hashtable是java标准库的方法,出来这两个以外其他的方法也有加锁的,有想法的友友们可以去看看这里就不详细说还有那些了

以上get和put方法来看就是直接对Hashtable对象本身加锁

(1)就是相当于多个线程访问Hashtable就会造成锁冲突。

(2)size也是加锁了的,现在就比较慢了

 但是同样解决了线程安全问题,这里画一个图来接解释可能的线程安全问题

 上图有有从两个方面看,只有一条链的时候有问题,但是是整个对象, 但凡来一个线程想要调度都会导致锁冲突,冲突概率太大了,及时是修改两条不同的链表也会产生冲突就很不划算

所以我们就开始说说ConcurrentHashMap针对这一情况的好处

3.2.2、ConcurrentHashMap

其实ConcurrentHashMap是很相近的,就是锁上的区别,锁实现于每个链表各自加锁(不是整个对象加锁,大家也就不公用一把锁)

这里还是花图解释

(1) ConcurrentHashMap的锁粒度变细了相比Hashtable中的锁粒度,但是早JDK1.8之前ConcurrentHashMap是:“分段锁”,顾名思义就是将数组分为2或者3个一组加锁,加锁的数量有所减少,但是仍然有可能多线程执行的时候会造成线程安全问题,不能排除多个线程呢访问的是同一个锁下的不同链表。(也就是问题二的情况)

(2)ConcurrentHashMap针对读操作不加锁,只针对写操作加锁(回顾锁相关)

<1>读操作和读操作之间是没有冲突的

<2>读操作和写操作之间是存在冲突的

<3>写操作和写操作之间是存在冲突的

其实读操作不加,只加锁写操作(synchronized加锁),会导致读了一个写了一半的结果(脏读),那是因为写操作不是“原子”但是ConcurrentHashMap使用了volatile+原子的写操作,保证从内存读取结果。

(3)ConcurrentHashMap内部充分的使用了CAS,CAS是特殊方式保证线程安全,减少了加减锁的操作次数

(4)针对扩容,采取了“化整为零”的方式

前面没有说Hashtable的扩容怎么样,因为他和HashMap是一样的。

创建一个更大的数组空间,把旧的数组上的链表上的每个元素都搬运到新的数组上(旧链表上产删除+新链表上插入),那既然有插入就会使用到put方法,那10个容量的哈希表很好快,但是如果个数多了呢,以亿做单位一次性put完够呛的,用户不会所有人都卡,但是总会有那么些幸运人员是比较卡的(想一下你打游戏的时候卡,感觉不那么舒服哈)。

这里是为了对比ConcurrentHashMap在扩容上的优越性

ConcurrentHashMap中,扩容采取的时候分成小部分进行扩容,进行多次完成全部扩容,创建新数组,也保留旧数组,每次put都会在新数组上添加,同时也进行小部分搬运,那现在get,也不会出现问题,get数据来源于旧数组和新数组,都会进行查询经过一段时间之后所有元素都搬运好了,最后释放旧数组

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

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

相关文章

【SpringMVC】使用SpringMVC处理JSON格式的数据

目录 一、前言 二、ResponseBody 三、RequestBody 四、HttpMessageConverter 相关文章&#xff08;可以关注我的SpringMVC专栏&#xff09; SpingMVC专栏SpingMVC专栏一、前言我们在使用Servlet处理前端请求&#xff0c;使用Json格式的数据&#xff0c;通常引入外部提供的一些…

拉伯证券|利好来了,145万手封涨停!低位+低市值“无主”股揭秘

二三四五或将完毕多年“无主”局势。 周末大消息不断。蚂蚁集团1月7日在官网发布关于持续完善公司管理的公告。公告显现&#xff0c;蚂蚁集团调整首要股东投票权&#xff0c;强化与阿里巴巴集团的隔离&#xff0c;阿里巴巴开创人马云抛弃了对蚂蚁集团的实践操控权&#xff0c;蚂…

JavaScript 原型链

文章目录原型链本质 - 对象间的关联关系instanceof 和 isPrototypeOf__proto__的大致实现委托原型链 原型链就是一系列对象的链接。通常来说&#xff0c;这个链接的作用是&#xff1a;如果在对象上没有找到需要的属性或者方法引用&#xff0c;引擎就会继续在[[Prototype]]关联的…

餐饮业数字化提速,OceanBase助海底捞变身“云上捞”

在海底捞火锅智慧餐厅&#xff0c;你会看到忙得团团转的机械臂和传菜机器人&#xff0c;顾客在智能点餐系统上下单&#xff0c;机械臂和传菜机器人就会着手备菜、传菜、上菜&#xff0c;服务人员则有更多时间专心应答顾客的各种询问。这种新模式&#xff0c;也为海底捞家喻户晓…

Springboot新手开发 基本总结

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;后端专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

网络实验之HSRP协议

一、HSRP协议简介 HSRP&#xff1a;热备份路由器协议&#xff08;HSRP&#xff1a;Hot Standby Router Protocol&#xff09;&#xff0c;是cisco平台一种特有的技术&#xff0c;是cisco的私有协议。该协议中含有多台路由器&#xff0c;对应一个HSRP组。该组中只有一个路由器承…

mac自定义环境变量,mac自定义python变量,自定义通用变量(任意名字)

文章目录几个常用的命令工作原理知道原理后已经有了环境变量没有该环境变量几个常用的命令 功能命令查看当前环境变量echo $PATH编辑文件&#xff08;注&#xff1a;这里是直接编辑bash_profile&#xff09;vi ~/.bash_profile编辑i退出编辑esc回到命令&#xff08;用命令来保…

Apollo实现cron语句的热配置

GitHub项目地址 Gitee项目地址 Apollo&#xff08;阿波罗&#xff09;是携程框架部门研发的分布式配置中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端&#xff0c;并且具备规范的权限、流程治理等特性&#xff0c;适…

stl algorithms 算法

所有泛型算法&#xff0c;除了少数例外&#xff0c;前两个参数均为一组iterator&#xff0c;用来标识欲遍历容器元素的范围&#xff0c;范围从第一个iterator所指位置开始&#xff0c;至第二个iterator所指位置&#xff08;并不包括&#xff09;结束 如 int arr[3]{1,2,3} ve…

管理客户信息并非易事

客户信息是企业的重要资产&#xff0c;是企业日积累月的价值沉淀&#xff0c;管理客户信息对于企业来说并不是一件容易的事&#xff0c;只有妥善管理客户信息&#xff0c;才能为企业创造更多价值。前言众所周知&#xff0c;客户信息是企业的重要资产&#xff0c;是企业日积累月…

rtu遥测终端机应用及安装介绍

1、设备介绍 设备集遥测终端机功能和视频录像机功能为一体&#xff0c;融合先进的3G/4G/WIFI通信技术、实现水文/水资源/环保212/TCP Modbus/MQTT等数据的采集、视频、图像存储、显示、控制、报警及传输等智能值守功能。 2、设备应用方向 本设备可广泛适用于带视频监测的水…

Jenkins 构建过程中提示 GPG 错误

错误信息如下&#xff1a;[INFO] --- maven-gpg-plugin:3.0.1:sign (sign-artifacts) rets-io --- gpg: no default secret key: No secret keygpg: signing failed: No secret key这个问题的原因应该是我们最近把我们的项目发布到中央 Maven 仓库中&#xff0c;但是发布项目到…

社区发现系列05:图的构建

想要挖掘作弊团伙首先先要构建社交网络图&#xff0c;然后用算法或者策略挖掘作弊团伙&#xff0c;那么如何构建社交网络图呢&#xff1f;下面给大家介绍一些实战经验&#xff0c;主要从电商和互金小额贷款两个业务场景来说&#xff1a; 1、电商业务 由于电商业务涉及的业务场…

《Spring揭秘》读书笔记1:IoC和AOP

1 Spring框架的由来 Spring框架的本质&#xff1a;提供各种服务&#xff0c;以帮助我们简化基于POJO的Java应用程序开发。 各种服务实现被划分到了多个相互独立却又相互依赖的模块当中&#xff1a; Core核心模块&#xff1a;IoC容器、Framework工具类。 AOP模块&#xff1a;S…

万物皆可灵活用工?灵活用工模式最契合的行业是哪些?

灵活用工VS传统用工 1&#xff09;传统用工 企业与员工签订劳动合同&#xff0c;双方出现问题纠纷适用于劳动法&#xff0c;关系固定&#xff0c;企业责任大。养着你&#xff0c;难&#xff0c;辞了你&#xff0c;更难。企业成本相对较高、负担较重&#xff08;薪资、福利&am…

Hudi的核心概念 —— 表类型(Table Types)

文章目录Copy On WriteMerge On ReadCOW 与 MOR 的对比查询类型&#xff08;Query Types&#xff09;Snapshot QueriesIncremental QueriesRead Optimized Queries不同表支持的查询类型Copy On Write 在 COW 表中&#xff0c;只有数据文件/基本文件&#xff08;.parquet&#…

【Java寒假打卡】Java基础-集合HashSet

【Java寒假打卡】Java基础-集合Set概述哈希值hashSet原理HashSet存储学生对象并遍历小结概述 底层数据结构是哈希表不能保证存储和去除的顺序完全一致没有带索引的方法&#xff0c;所以不能使用普通的for循环进行遍历 使用增强for循环或者迭代器进行遍历由于是Set集合&#xf…

广告业务系统 之 承前启后 —— “消息中心”

文章目录广告业务系统 之 承前启后 —— “消息中心”消息中心物料同步链路图模块设计之 “一分为二”模块交互图之 “强一致性设计”奇怪交互图的数据链路数据一致性问题日志中心广告业务系统 之 承前启后 —— “消息中心” 消息中心 消息中心&#xff0c;是为 投放引擎 做…

韩顺平老师的Linux基础学习笔记 (上)

Linux学习笔记 前言&#xff1a;本系列笔记的参考由 2021韩顺平 一周学会Linux 总结而成&#xff0c;希望能给学习Linux的同学一些帮助。也感谢韩老师录制的视频给我带来了非常巨大的收获&#xff01; 目录&#xff1a; 韩顺平老师的Linux基础学习笔记 (上)韩顺平老师的Linu…

交通部互通互联二维码之发卡机构公钥证书

背景 随话说的好啊&#xff0c;好比不如烂笔头&#xff0c;之前开发联调OK后&#xff0c;闲置了半年&#xff0c;结果今天再去搞公钥&#xff0c;发现完全忘记了生成规则。审核 有病 哪里来的广告&#xff1f; 特此&#xff0c;记录一下我们的过程&#xff0c;以便后面再出现…