多线程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知识,请关注本博主~~

相关推荐
是程序喵呀4 分钟前
SpringMVC详解
java·spring·spring-mvc
疯一样的码农9 分钟前
Apache Maven 标准文件目录布局
java·maven·apache
Felix666yy29 分钟前
设计模式之建造者模式
java
&梧桐树夏29 分钟前
JavaEE 线程安全
java-ee·多线程
界面开发小八哥30 分钟前
「Java EE开发指南」如何使用Visual JSF编辑器设计JSP?(一)
java·ide·java-ee·编辑器·myeclipse
先睡32 分钟前
javaEE
java·java-ee
程序猿小D40 分钟前
第三百三十一节 Java网络教程 - Java网络UDP多播
java·网络·udp
灭掉c与java43 分钟前
第五章springboot实现web的常用功能
java·spring boot·spring
初晴~1 小时前
【Spring】RESTful设计风格
java·后端·spring·springboot·restful
风动也无爱1 小时前
Java的正则表达式和爬虫
java·爬虫·正则表达式