JUC包下面的四大天王+线程池部分知识

一)Semphore:限流器用我就对了

Java中信号量Semphore是把操作系统原生的信号量封装了一下,本质就是一个计数器,描述了 可用资源的个数,主要涉及到两个操作

如果计数器为0了,继续Р操作,就会出现阻塞等待的情况

P操作:申请一个可用资源,计数器-1
V操作:释放一个可用资源,计数器+1

停车场门口有一个灯牌,会显示停车位还剩余多少个,每进去一辆车,显示的停车位数量就-1,就相当于进行了一次P操作,每出去一辆车, 显示的停车位数量就+1,就相当于进行了一次V操作,而当停车场的剩余车位为0时,显示的停车位数量就为0了;

1)创建Semaphore示例, 初始化为4, 表示有4个可用资源.

2)acquire方法表示申请资源(P操作), release方法表示释放资源(V操作)

public class Main{
    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(10);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"开始申请资源");
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"已经获取到资源了");
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+"开始释放资源了");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        for(int i=0;i<10;i++){
            Thread t=new Thread(runnable);
            t.start();
        }
    }
}
public class Main{
    public 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 < 10000; i++) {
                try {
                    semaphore.acquire();
                    count++;
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<10000;i++){
            try {
                semaphore.acquire();
                count++;
                semaphore.release();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }});
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);

    }
}

基于Semphore可以实现限流器:

什么是限流:比如说某一个广场,他的日载流量是6W,那么如果说假设有一天来了10W人,但是只能进去6W人,这个时候就只能排队入园了,因为工作人员始终会将人数控制在6W人

咱们再从生活中的事例回到程序当中,假设一个程序只能为 10W 人提供服务,突然有一天因为某个热点事件,造成了系统短时间内的访问量迅速增加到了 50W,那么导致的直接结果是系统崩溃,任何人都不能用系统了,显然只有少人数能用远比所有人都不能用更符合预期,因此这个时候要使用限流了

Semphore本身是依靠计数器的思想来进行实现的,它可以控制对于共享资源的访问数量,当线程需要访问该资源的时候,他必须先进行获取一个许可,就是从计数器中获取到资源

当计数器本身大于0的时候,线程可以获取到这个可用资源并且能够继续执行

当计数器本身等于0的时候,线程将会被阻塞,直到有其他的线程释放资源

Semphore本身有两个重要的操作,acquire()和realse()操作

1)当线程需要访问共享资源的时候,它会调用acquire方法来获取资源,如果计数器的值大于0,那么acquire()方法会将计数器的值减1,并且允许线程继续运行,如果计数器的值等于0,那么acquire()方法会使得线程阻塞,知道有其他线程释放资源

2)当线程使用完成共享资源以后,该线程可以调用realse方法来释放资源,realse()方法会使得计数器的值+1,表示有一个资源可以使用,其他被阻塞的线程可以有机会获得可用资源并且+1;

关于公平模式和非公平模式:

在这里面所谓的公平模式就是说线程调用acquire的先后顺序来获取到这个可用资源的,公平模式遵循先进先出原则,所以非公平模式是抢占式的,也就是说有可能一个新的获取线程恰好在一个许可证释放以后得到了这个许可证,但是这个已经获取许可证的线程前面还存在着一些其他的线程,当然在这里面非公平模式的性能比较高;

假设说,当有时候需要等待某一些线程执行完成了之后,再来执行主线程的代码,此时应该怎么做呢?可能有人会说,简单,用 join() 方法等待线程执行完成之后再执行主线程就行了,当然,如果使用的是 Thread 来执行任务,那这种写法也是可行的。然而真实的(编码)环境中我们是不会使用 Thread 来执行多任务的,而是会使用线程池来执行多任务,这样可以避免线程重复启动和销毁所带来的性能开销;

二)CountDownLatch:别急,等人齐了在开团

撞线:调用latch.countDown()

比赛结束,统计成绩:latch.await(),只要还存在着有任意的一个选手不进行撞线,那么比赛就无法结束,只有说所有的选手比赛撞了线,那么最终的比赛才可以结束

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(10);
        for(int i=0;i<10;i++){
            Thread t=new Thread(()->{
                System.out.println("线程"+Thread.currentThread().getName()+"开始起跑");
                try {
                    Thread.sleep(new Random().nextInt(10000));
                System.out.println("线程"+Thread.currentThread().getName()+"开始撞线");
                    latch.countDown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        latch.await();
        System.out.println("比赛完成");
    }
}

1)使用CountDownLatch可以实现等待所有任务执行完成以后再来执行主任务的功能,他就是类似于说好像比赛中等待所有运动员都完成比赛以后再来公布排名一样,当然咱们在大玩着荣耀的时候也是一样,只有说所有人集合完毕以后在开团

2)而CountDownLatch就是通过计数器来实现等待功能的,当创建CountDownLatch的时候会创建一个大于0的计数器,每一次调用countDown()方法的时候计数器的值会减1,直到计数器的值变成0以后,等待的任务就可以继续执行了

3)countDownLatch在底层实现的时候是依靠内部创建并维护了一个voltaile的计数器,当调用countDown()方法的时候,会尝试将整数计数器-1,CountDownLatch 在创建的时候需要传入一个整数,在这个整数“倒数”到 0 之前,主线程需要一直挂起等待,直到其他的线程都执行之后,主线才能继续执行

    public static void main(String[] args) throws InterruptedException {
      //创建CountDownLatch实现两个计数器
        CountDownLatch latch=new CountDownLatch(2);
        //创建线程池执行任务
        ExecutorService service= Executors.newFixedThreadPool(2);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是线程池提交的第一个任务");
                latch.countDown();
            }
        });
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是线程池提交的第二个任务");
                latch.countDown();
            }
        });
        latch.await();
        System.out.println("线程池中的任务已经全部执行完成");
    }

三)循环栅栏(Cycbarrier):人齐了老司机就可以发车了

循环栅栏实现一个可以循环利用的屏障

https://img-blog.csdnimg.cn/img_convert/f10e1adb034e3ebaa2c02b11596386ee.gif

1)CycliBarrier作用是让一组线程之间可以相互等待,当到达一个共同点的时候,所有之前等待的线程会冲破栅栏,一起向下执行

2)现在举个例子来说:伟哥要做末班车回家,公交车站的司机会等待车上面的所有乘客坐满以后再来发车,还有比如说王者荣耀,得等待5个队友游戏都加载完了才可以进入到游戏

3)本质上来说是让多个线程共同相互等待,知道说当所有的线程都到达了屏障点以后,之前的所有线程才可以继续向下执行,CycBarrier本身就象老司机开车一样,如果车上面还有空闲的座位,那么司机就得等着,只有说当作为坐满以后,老司机才发车

    public static void main(String[] args) {
        CyclicBarrier barrier=new CyclicBarrier(10, new Runnable() {
            @Override
            public void run() {
                System.out.println("现在司机上面的人都到齐了开始进行发车");
                System.out.println("当前线程池中的任务都已经执行完成了");
            }
        });
        ExecutorService service=Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(5000));
                        System.out.println("当前乘客开始上车"+Thread.currentThread().getName());
                        barrier.await();//当前判断线程池中的任务执行完成可以执行多次
                        System.out.println("当前线程下车"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (BrokenBarrierException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }
}

在CycliBarrier底层是基于计数器来实现的,当count不为0的时候,每一个线程在到达屏障点以后会先进行调用await()方法将自己阻塞,此时计数器会减1,此时这个线程会阻塞在这个屏障处,当循环栅栏的计数器被减为0的时候,所有调用await()的线程就会被唤醒,就会冲破栅栏,一起执行,CountDownLatch和CycliBarrier在底层都是依靠计数器来实现的,但是CountDownLatch只能使用一次,但是CycliBarrier却可以使用多次,这就是两者最大的区别

总结:CycliBarrier在底层是依靠ReentranLock来实现计数器的原子性更新的,CycliBarrier最常使用的就是await()方法,使用该方法就会将计数器的值减1,并判断当前的计数器是否为0,如果不是0就阻塞等待,并且当计数器变成0以后,该线程也就是阻塞在循环栅栏的线程才可以继续执行剩余任务;

三)线程池的状态:

1)Running状态:运行状态,线程池创建完成以后就进入到这个状态,如果不手动调用关闭方法,那么线程池在整个程序运行过程中都是这个状态;

2)ShutDown状态:关闭状态,线程池本身不再接受新任务的提交,但是会有先将线程池中已经存在的任务处理完成

3)Stop停止状态:不再接受新任务的提交,并且会中断正在执行的任务,放弃任务队列中已经存在的任务

4)tidying状态:整理状态,所有的任务都执行完成以后,也包括任务队列中的任务执行完成,当前线程池中的活动线程数降为0的状态,到达此状态以后会调用线程池的terminated方法

5)terminated状态:销毁状态,当调用线程池的terminated方法以后会进入到这个状态

      ThreadPoolExecutor executor=new ThreadPoolExecutor(10, 10,
                100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        }){
            @Override
            protected void terminated() {
                super.terminated();
                System.out.println("线程池终止");
            }
        };
    

1)当进行调用shutDown方法的时候,线程池中的状态会由Running状态到达shutDown状态,最后在到达tidying状态,最后到达terminated状态

2)当进行调用shutDownNow方法的时候,线程池中的状态会由running状态到达stop状态,最后在到达tidying状态,最后到达terminated状态

3)进行调用terminated方法,线程池会直接从tidying状态到达terminated状态,可以在阻塞队列的时候重写此方法,默认来说这个方法是空的

四)如何判断线程池中的任务都已经执行完成了?

1)在很多场景下,都希望等待线程池中的所有任务都执行完,然后再来执行下一步操作,对于Thread类来说,这样的实现是很简单的,加上一个join方法就解决了,但是对于线程池的判断就比较麻烦了

2)从上面的执行结果可以看出来,程序先打印了任务执行完成,再来继续打印并执行线程池的任务,这种执行顺序混乱的结果不是我们想要看到的,我们期望的结果就是等到鲜橙汁中的所有任务都执行完成了,再来进行打印线程池执行完成的信息;

3)产生少数问题的原因就是主线程main和线程池是并发执行的,所以说当线程池还没有执行完main现成的打印结果就已经执行了,想要解决这个问题就需要在打印结果之前,先判断线程池中的任务是否已经执行完成,如果没有执行完成就等到任务执行完成再来打印结果

 public static void main(String[] args) {
        ExecutorService service=Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("开始执行线程池中的任务");
                }
            });
        }
        System.out.println("线程池中的所有任务执行完成");
    }
1)使用isTerminated()方法来判断:

1)使用线程池的终止状态来进行判断线程池中的任务是否已经全部执行完成,但是如果想要让线程之中的状态改变就需要调用shutDown()方法,不然线程池会一直处于Running运行状态那么就没有办法来进行判断是否处于终止状态来判断线程池中的任务是否已经全部执行

2)shutdown方法是启动线程池有序关闭的方法,它在关闭之前会执行完成所有已经提交的任务,并且不会再进行接收新的任务,当线程池中的所有任务都执行完成以后,线程池就处于终止状态了,此时isTerminated()方法返回的结果也就是true了;

缺点:需要关闭线程池

 ThreadPoolExecutor executor=new ThreadPoolExecutor(10, 10,
                100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        }){
            @Override
            protected void terminated() {
                super.terminated();
                System.out.println("线程池终止");
            }
        };
        executor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务1");
            }
        });
        executor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务2");
            }
        });
        executor.shutdown();
        while(!executor.isTerminated()){

        }
        System.out.println("线程池中的任务已经执行完成了");

    
2)判断getCompletedTaskCount和getTaskCount是否相等

getTaskCount()返回执行计划任务的总数,但是因为本身任务和线程的状态都在不断地发生变化,因此返回的值是一个近似值;

getCompetedTaskCount()返回完成执行的任务总数,但是因为本身任务和线程的状态都在不断地发生变化,因此返回的值是一个近似值,但是在连续的调用中并不会减少

虽然不需要关闭线程池,但是可能会造成一定的误差

3)调用countDownLatch和CycliBarrier

需要注意的是countDownLatch中的countDown()方法和CycliBarrier中的await()方法需要在线程池的run方法的最后调用

4)使用FutureTask

FutureTask中的优势就是判断比较精准,调用每一个线程的FutureTask的get方法就是等待该任务执行完成的,需要使用submit进行提交:

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor executor=new ThreadPoolExecutor(10,10,0,TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));
       FutureTask<Integer> task1=new FutureTask<>(new Callable<Integer>() {
           @Override
           public Integer call() throws Exception {
               int a=10;
               a++;
               System.out.println("a++完成");
               return a;
           }
       });
        FutureTask<Integer> task2=new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int b=11;
                b++;
                System.out.println("b++完成");
                return b;
            }
        });
      executor.submit(task1);
      executor.submit(task2);
      Integer result1= task1.get();
      Integer result2=task2.get();
        System.out.println("线程池中的任务都已经执行完成");
    }

五)submit和execute的区别:

1)接收到的参数不同:submit方法只能接受到runnable接口的任务,但是submit方法及可以接受到runnable方法的任务,也可以接收到callable,futureTask类型的任务,前者没有返回值,后者可以后返回值;

2)execute()的返回值是void,线程提交后不能得到线程的返回值,submit()的返回值是Future,通过Future的get()方法可以获取到线程执行的返回值,get()方法是同步的,执行get()方法时,如果线程还没执行完,会同步等待,直到线程执行完成

注意:虽然submit()方法可以提交Runnable类型的参数,但执行Future方法的get()时,线程执行完会返回null,不会有实际的返回值,这是因为Runable本来就没有返回值

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ribbon 负载均衡原理和策略

目录 一、Ribbon 是什么 二、Ribbon 负载均衡原理 三、Ribbon 负载均衡策略 四、Ribbon的应用场景 一、Ribbon 是什么 Ribbon是一个开源的、基于HTTP和TCP的客户端负载均衡工具&#xff0c;它提供了一个简单的、基于配置的负载均衡策略&#xff0c;可以帮助开发人员更轻松…

苹果M3处理器跑分曝光,单核无敌!

10月底&#xff0c;苹果发布了全新的M3、M3 Pro、M3 Max芯片以及搭载M3系列芯片的3款新硬件&#xff0c;包括&#xff1a;新款24英寸iMac、新款14/16英寸MacBook Pro。 根据苹果官方介绍&#xff0c;M3系列芯片基于台积电3纳米工艺打造&#xff0c;采用全新图形处理器架构&…

【电工基础】

电工基础 11.1 简介1.2 电路作用1.3 电路模型1.4 电流定义1.5 电压定义1.6 电动势1.7 电阻元件1.7.1 电阻元件定义1.7.2 电阻原件的特性1.7.31.7.4 1.81.91.10 345 1 1.1 简介 电源外部&#xff0c;正电荷移动的方向是由电源正极向电源负极方向&#xff0c;负电荷移动的方向是…

C语言--输入10个数字,要求输出其中值最大的元素和该数字是第几个数

今天小编带大家了解一下什么是“打擂台”算法。 一.思路分析 可以定义一个数组arr&#xff0c;长度为10&#xff0c;用来存放10个数字&#xff0c;设计一个函数Max&#xff0c;用来求两个数中的较大值&#xff0c; 定义一个临时变量tmparr[0],保存临时最大的值&#xff0c;下标…

MySQL数据库基础和操作

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; MYSQL数据库 &#x1f319;请不要相信胜利就像山坡上的蒲公英一…

平衡树相关笔记

引入 二叉查找树 二叉查找树&#xff08;Binary Search Tree&#xff09;&#xff0c;又名二叉搜索树。满足以下性质&#xff1a; 对于非空的左子树&#xff0c;左子树点权值小于根节点。对于非空的右子树&#xff0c;左子树点权值大于根节点。二叉查找树的左右子树均是二叉…

C# ZXing 二维码,条形码生成与识别

C# ZXing 二维码条形码生成识别 安装ZXing使用ZXing生成条形码生成二维码生成带Logo的二维码识别二维码、条形码 安装ZXing NuGet搜索ZXing安装ZXing.Net包 使用ZXing using ZXing; using ZXing.Common; using ZXing.QrCode; using ZXing.QrCode.Internal; 生成条形码 //…

API SIX系列-服务搭建(一)

APIsix简介 APISIX是一个微服务API网关&#xff0c;具有高性能、可扩展性等优点。它基于nginx&#xff08;openresty&#xff09;、Lua、etcd实现功能&#xff0c;借鉴了Kong的思路。和传统的API网关相比&#xff0c;APISIX具有较高的性能和较低的资源消耗&#xff0c;并且具有…

认识继承和多态

1 继承 1.1 为什么需要继承 Java 中使用类对现实世界中实体来进行描述&#xff0c;类经过实例化之后的产物对象&#xff0c;则可以用来表示现实中的实体&#xff0c;但是现实世界错综复杂&#xff0c;事物之间可能会存在一些关联&#xff0c;那在设计程序里就需要考虑 比如&a…

android的gif图片解析器讲解

先来了解一下Gif图片的构成 大概是这样的组成 GIF图片的编码结构中各部分所占字节数的具体情况如下&#xff1a; 文件头&#xff08;File Header&#xff09;&#xff1a;通常占据6个字节。其中&#xff0c;GIF标识符 “GIF” 占3个字节&#xff0c;版本信息&#xff08;如 “8…

品牌滥用申诉

指导 据了解&#xff0c;有以下几种情况可能会出现品牌滥用&#xff1a; 第一种&#xff1a;店铺存在问题 包括但不限于以下问题&#xff1a;店铺绩效中有感叹号、店铺 rating 少于 4.5 分、ODR 超标、被变狗 过、二手货投诉、商标版权专利侵权等。 第二种&#xff1a;品牌授…

消息队列使用场景

&#x1f388;个人公众号:&#x1f388; :✨✨✨ 可为编程✨ &#x1f35f;&#x1f35f; &#x1f511;个人信条:&#x1f511; 知足知不足 有为有不为 为与不为皆为可为&#x1f335; &#x1f349;本篇简介:&#x1f349; 本篇记录消息队列使用场景&#xff0c;如有出入还望…

《红蓝攻防对抗实战》十.内网穿透之利用DNS协议进行隧道穿透

一.利用DNS协议进行隧道穿透 1.环境配置2.Windows系统下进行DNS隧道穿透利用3.Linux系统下进行DNS隧道穿透利用 前文推荐&#xff1a; 《红蓝攻防对抗实战》一. 隧道穿透技术详解 《红蓝攻防对抗实战》二.内网探测协议出网之TCP/UDP协议探测出网 《红蓝攻防对抗实战》三.内网…

LeetCode【33】搜索旋转排序数组

题目&#xff1a; 思路&#xff1a; https://www.cnblogs.com/CherryTab/p/12196580.html 代码&#xff1a; class Solution {int [] nums;int target;public int find_rotate_index(int left, int right) {if (nums[left] < nums[right])return 0;while (left < righ…

【推荐】一款AI写作大师、问答、绘画工具-「智元兔 AI」

在当今技术飞速发展的时代&#xff0c;越来越多的领域开始应用人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;。其中&#xff0c;AI写作工具备受瞩目&#xff0c;备受推崇。在众多的选择中&#xff0c;智元兔AI是一款在笔者使用过程中非常有帮助的…

利用Python代码提取shp中每个区域的图像

import geopandas as gpd import rasterio from rasterio.mask import mask import matplotlib.pyplot as plt import numpy as np# 载入shp文件 - 它只包含几何对象 shapefile_path rD:\Desktop\新建文件夹 (3)\01.shp shapes gpd.read_file(shapefile_path)# 打开图像 imag…

从零开始开发抖音小程序:与餐饮团购的完美融合

本文将探讨如何从零开始开发一个创新的抖音小程序&#xff0c;以其独特的特性与餐饮团购进行完美融合。 一、什么是抖音小程序&#xff1f; 抖音小程序为开发者提供了在用户观看视频时进行无缝体验的机会。通过借助抖音的庞大用户基础&#xff0c;开发者可以将自己的创意呈现给…

通过easyexcel导出数据到表格

这篇文章简单介绍一下怎么通过easyexcel做数据的导出&#xff0c;使用之前easyui构建的歌曲列表crud应用&#xff0c;添加一个导出按钮&#xff0c;点击的时候直接连接后端接口地址&#xff0c;在后端的接口完成数据的导出功能。 前端页面完整代码 let editingId; let request…

保姆级自定义GPTs教程,无需任何代码!

11月10日&#xff0c;OpenAI正式宣布向所有ChatGPT Plus用户开放GPTs功能&#xff0c;一个人人都能开发自定义ChatGPT助手的时代降临。 GPTs支持无代码、可视化点击操作&#xff0c;这意味着即便你没有任何编程经验&#xff0c;只要有数据、脑洞大开的想法&#xff0c;就能开发…

为什么用Selenium做自动化测试,你真的知道吗?

手工测试的问题 手工操作点点点借助的是人脑的反应和聪明&#xff0c;为什么不用手点了呢&#xff1f;手会酸&#xff0c;脑子会累&#xff0c;会占据太多的时间。想一想为什么会学习自动化测试。我们都希望通过工具来解放我们的双手&#xff0c;大脑&#xff0c;眼睛。 为什么…
最新文章