多线程4:线程池、并发、并行、综合案例-抢红包游戏

欢迎来到"雪碧聊技术"CSDN博客!

在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将不断探索Java的深邃世界,分享最新的技术动态、实战经验以及项目心得。

让我们一同在Java的广阔天地中遨游,携手提升技术能力,共创美好未来!感谢您的关注与支持,期待在"雪碧聊技术"与您共同成长!

目录

一、认识线程池

1、什么是线程池?

2、不复用线程的问题

3、线程池的工作原理

①工作线程

②任务队列

二、创建线程池

1、如何创建线程池?

方式一:JDK5.0开始,提供了代表线程池的接口:ExecutorService。使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

举例:

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

2、线程池的注意事项

①什么时候开始创建临时线程?

②什么时候会拒绝新任务?

3、任务拒绝策略

三、处理Runnable任务

1、ExecutorService的常用方法

2、总结

四、处理Callable任务

1、ExecutorService的常用方法

2、总结

五、通过Executors工具类,创建线程池

1、该工具类中有哪些静态方法?

2、Executors工具类使用时可能存在的风险

3、总结

六、并发、并行

1、什么是进程?

举例

2、什么是线程?

3、什么是并发?

举例

4、什么是并行?

举例

5、总结

举例

六、综合案例-抢红包游戏

1、介绍

2、编码


一、认识线程池

1、什么是线程池?

线程池就是一个可以复用线程的技术。

2、不复用线程的问题

用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理,创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

就好比:每次吃完饭都扔一个碗,长期以来肯定吃不消这么大的消耗。

3、线程池的工作原理

①工作线程

工作线程:就是线程池里面的线程,来处理任务队列里面的任务,有点类似于饭店的服务员。

②任务队列

任务队列:里面存放的都是实现了Runnable/Callable接口的任务类,等待工作线程的完成。

二、创建线程池

1、如何创建线程池?

方式一:JDK5.0开始,提供了代表线程池的接口:ExecutorService。使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

该实现类 ThreadPoolExecutor的构造器一共有七个参数,如下:

举例:
java 复制代码
public class Test9 {
    public static void main(String[] args) {
        //目标:创建线程池对象来使用
        //1、使用线程池(ExecutorService接口)的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,10, 
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), 
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    }
}

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

下面五会讲。

2、线程池的注意事项

①什么时候开始创建临时线程?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

②什么时候会拒绝新任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

3、任务拒绝策略

三、处理Runnable任务

1、ExecutorService的常用方法

举例:

java 复制代码
public class Test9 {
    public static void main(String[] args) {
        //目标:创建线程池对象来使用
        //1、使用线程池(ExecutorService接口)的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程池处理任务!看会不会复用线程?
        Runnable target = new My_Runnable();//任务对象
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //3、关闭线程池:一般不关闭线程池
        //pool.shutdown();//等所有任务执行完毕后,再关闭线程池!
        //pool.shutdownNow();//立即关闭,不管任务是否执行完毕!
    }
}

//线程任务类(等待被线程对象执行的任务)
class My_Runnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行了~~");
    }
}

执行结果:

java 复制代码
pool-1-thread-2执行了~~
pool-1-thread-3执行了~~
pool-1-thread-2执行了~~
pool-1-thread-3执行了~~
pool-1-thread-2执行了~~
pool-1-thread-1执行了~~

可见此时6个任务,但是线程池就3个核心线程,因此会产生复用。于是就看到上面确实有很多线程执行了好几个任务,因此线程确实是被复用了。

2、总结

四、处理Callable任务

1、ExecutorService的常用方法

举例:

java 复制代码
public class Test10 {
    public static void main(String[] args) {
        //目标:创建线程池对象来使用
        //1、使用线程池(ExecutorService接口)的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程池处理任务!看会不会复用线程?
        Future<String> f1 = pool.submit(new My_Callable(100));
        Future<String> f2 = pool.submit(new My_Callable(200));
        Future<String> f3 = pool.submit(new My_Callable(300));
        Future<String> f4 = pool.submit(new My_Callable(400));
        
        //输出线程执行完,返回的结果
        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        //3、关闭线程池:一般不关闭线程池
        //pool.shutdown();//等所有任务执行完毕后,再关闭线程池!
        //pool.shutdownNow();//立即关闭,不管任务是否执行完毕!
    }
}

//1、定义一个实现Callable接口的实现类
class My_Callable implements Callable<String>{
    private int n;
    public My_Callable(int n){
        this.n = n;
    }

    //2、重写call方法,定义线程执行体
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= n; i++) {
            sum+=i;
        }
        return Thread.currentThread().getName()+"-"+n+"的和是:"+sum;
    }
}

运行结果:

java 复制代码
pool-1-thread-1-100的和是:5050
pool-1-thread-2-200的和是:20100
pool-1-thread-3-300的和是:45150
pool-1-thread-1-400的和是:80200

可见此时线程1被复用。

2、总结

五、通过Executors工具类,创建线程池

1、该工具类中有哪些静态方法?

举例:

java 复制代码
public class Test10 {
    public static void main(String[] args) {
        //1、通过线程池工具类:Executors,调用其静态方法直接得到线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        //2、使用线程池处理任务!看会不会复用线程?
        Future<String> f1 = pool.submit(new My_Callable(100));
        Future<String> f2 = pool.submit(new My_Callable(200));
        Future<String> f3 = pool.submit(new My_Callable(300));
        Future<String> f4 = pool.submit(new My_Callable(400));

        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        //3、关闭线程池:一般不关闭线程池
        //pool.shutdown();//等所有任务执行完毕后,再关闭线程池!
        //pool.shutdownNow();//立即关闭,不管任务是否执行完毕!
    }
}

//1、定义一个实现Callable接口的实现类
class My_Callable implements Callable<String>{
    private int n;
    public My_Callable(int n){
        this.n = n;
    }

    //2、重写call方法,定义线程执行体
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= n; i++) {
            sum+=i;
        }
        return Thread.currentThread().getName()+"-"+n+"的和是:"+sum;
    }
}

运行结果:

java 复制代码
pool-1-thread-1-100的和是:5050
pool-1-thread-2-200的和是:20100
pool-1-thread-3-300的和是:45150
pool-1-thread-3-400的和是:80200

2、Executors工具类使用时可能存在的风险

3、总结

六、并发、并行

1、什么是进程?

正在运行的程序(软件)就是一个进程。

举例

2、什么是线程?

线程属于进程,一个进程可以同时运行很多个线程。

进程中的多个线程是并发+并行执行的。

3、什么是并发?

并发:进程中的线程是由CPU负责调度执行的,但CPU同时处理线程的数量是有限的(由CPU的核数决定),为了能保证全部线程都能往前执行,因此CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉是这些线程在同时执行,这就是并发。

举例

假设我的CPU是单核的(同一时刻只能运行一个线程),但是会让很多线程轮流切换上CPU执行,切换速度极快,仿佛是同时在运行所有进程中的线程,这就叫"并发"。

4、什么是并行?

并行:在同一个时刻上,同时有多个线程(数量取决于CPU的核数)在被CPU调度执行。

举例

假设我的CPU是4核,那么同一时刻,会有4个线程同时上cpu执行,这就叫"并行"。

说白了,并行的线程数,取决于CPU的核数。

5、总结

举例

通过查看某人电脑的任务管理器,得知该电脑的CPU是20核的,也就是说,同一时刻允许20个线程上CPU执行,那么这20个线程就是并行关系;

该电脑目前需要处理6800个线程,那么就会20个一组上CPU轮流切换运行,由于切换速度极快,仿佛是同时在运行,这就叫"并发"。

六、综合案例-抢红包游戏

1、介绍

2、编码

java 复制代码
public class Test12 {
    public static void main(String[] args) {
        /**
         * 目标:完成多线程的综合小案例
         * 红包雨游戏:某企业有100名员工,员工的工号依次是1,2,3,4...到100
         * 现在公司举办了年会活动,活动中有一个红包雨环节,要求共计发出200个红包雨,其中小红包在【1 - 30】 元之间
         * 总占比80%,大红包在【31 - 100】元, 总占比20%
         * 分析:100个员工实际上就是100个线程,来竞争200个红包
         */
        List<Integer> redPacket = getRedPacket();
        //2、定义线程类,创建100个线程,竞争同一个集合
        for (int i = 1; i <= 100; i++) {
            new EmployeeGetPacket(redPacket, "员工"+i).start();
        }
    }

    //1、准备这200个随机的红包返回,放到这个List集合中
    public static List<Integer> getRedPacket(){
        Random r = new Random();
        //其中小红包在【1 - 30】 元之间,总占比80%(即160个),大红包在【31 - 100】元,总占比为20%(即40个)
        ArrayList<Integer> redPacket = new ArrayList<>();
        for (int i = 1; i <= 160; i++) {
            redPacket.add(r.nextInt(30) + 1);
        }
        for (int i = 1; i <= 40; i++) {
            redPacket.add(r.nextInt(70) + 31);
        }
        return redPacket;
    }
}

//员工抢红包的线程
class EmployeeGetPacket extends Thread{
    private List<Integer> redPacket;//红包集合

    //构造器
    public EmployeeGetPacket(List<Integer> redPacket, String name) {
        super(name);
        this.redPacket = redPacket;
    }

    public void run() {
        String name = Thread.currentThread().getName();
        while(true){
            //100个人来抢redPackage集合中的钱
            synchronized (redPacket){
                if(redPacket.size() == 0){
                    break;
                }
                //随机一个索引得到红包
                int index = (int)(Math.random() * redPacket.size());
                Integer money = redPacket.remove(index);
                System.out.println(name + "抢到了" + money + "元");
                if(redPacket.size() == 0){
                    System.out.println("活动结束!");
                    break;
                }
            }
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                throw new RuntimeException(e);
            }
        }
    }
}

运行结果:

java 复制代码
员工1抢到了3元
员工99抢到了20元
员工98抢到了59元
员工100抢到了14元
员工96抢到了2元
员工97抢到了17元
员工95抢到了20元
员工94抢到了12元
员工93抢到了8元
员工92抢到了26元
员工91抢到了7元
...不全部展示了,因为200条抢红包记录,太长了...

以上就是线程池、并发、并行、抢红包游戏案例的全部内容,想了解更多Java知识,请关注本博主~~

相关推荐
腥臭腐朽的日子熠熠生辉42 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian44 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen2 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端