java----多线程高级应用之线程池

java----多线程高级应用之线程池,第1张

目录
    • 多线程高级应用线程池
      • 1.线程池
        • 1.1引言
        • 1.2什么是线程池
        • 1.3常用的线程池接口和类(包含在java.util.concurrent)里面
        • 1.4案例,利用线程池实现四个窗口共卖一百张票
        • 1.5补充`newScheduleThreadPool()`调度线程池
          • 1.5.1 延迟执行schedule函数
          • 1.5.2固定周期执行`scheduleAtFixedRate`
          • 1.5.3固定延迟执行scheduleWithFixedDelay
          • 1.5.4 固定周期执行和固定延迟执行的区别
      • 2.总结

多线程高级应用线程池 1.线程池 1.1引言

现有问题:
每一个线程都是宝贵的内存资源,单个线程大约要占1MB(主要是因为线程都要有自己单独的栈空间)的空间,因此,如果过多的分配空间给创建的线程容易造成内存溢出。
频繁的创建线程和销毁线程会增加虚拟机回收频率、资源开销,造成程序的性能下降。

1.2什么是线程池

是一个线程容器,可以设定线程分配的数量的上限
讲预先创建好的线程对象存入池中,并重用线程池中的线程对象
线程池可以有效避免线程的频繁创建和销毁。

1.3常用的线程池接口和类(包含在java.util.concurrent)里面
 1. Executor:线程池的顶级接口
 2. ExecutorService:线程池接口,可以通过submit提交任务代码
 3. Executors工厂类:通过此类可以获取一个线程池
 4. 通过newFixedThreadPool(int nThreads)获取固定数量的线程池,参数的含义是制定线程池中现成的数量
 5. 通过newCacheThreadPool() 获得动态数量的线程池,如果不够就创建新的,无上限。
1.4案例,利用线程池实现四个窗口共卖一百张票

这里是直接拿匿名内部类写的行为,不要忘记对票加锁
创建的是固定数目的线程池,由于现在案例是四个窗口共卖一百张票,所以内容需要用submit提交四次任务。
最后不要忘记执行线程池关闭函数。

代码

 public static void main(String[] args) {
        //创建行为
        Runnable ticket = new Runnable() {
            @Override
            public void run() {
                synchronized (this){
                    for(int i = 0;i < 100; i++){
                        System.out.println(Thread.currentThread().getName() + "卖了第" + i + "几张票" );
                    }
                }

            }
        };

        //创建线程池
        //创建的是固定线程池,池内线程数目是4
        ExecutorService es = Executors.newFixedThreadPool(4);
        //向创建的线程池内提交任务

        for (int i = 0;i < 4; i ++){  //由于我们要求是四个窗口,所以需要提交四次任务,但是无论提交几次任务,线程池内创建线程的最大数依旧是4个
            es.submit(ticket);
        }

        //关闭线程池
        es.shutdown();
        //此处不推荐使用shutdownNow函数,因为会出现不可预知的错误

        //添加这个循环是为了,等到线程池的任务执行完成之后,在继续执行下面的那句输出
        //相当于是让主线程一直在空空的运行一个函数
        while (!es.isTerminated()){}

        System.out.println("程序结束了.....");

    }

还有一种是利用缓存线程池,缓存线程池不限定线程创建的个数。
当有任务需要线程了就去创建,但是并不是每一个任务都会去创建一个创建一个线程,比如说,你可能提交了一百个任务,但是当你执行到一部分的时候前面的线程空出来了,这个时候,线程池就不会新创建线程了。
直接看代码:

 public static void main(String[] args) {
        //创建行为
        Runnable ticket = new Runnable() {
            @Override
            public void run() {
                synchronized (this){
                    for(int i = 0;i < 100; i++){
                        System.out.println(Thread.currentThread().getName() + "卖了第" + i + "几张票" );
                    }
                }

            }
        };

        //创建线程池
        //创建的是固定线程池,池内线程数目是4
        //ExecutorService es = Executors.newFixedThreadPool(4);
        //创建缓存线程池
        ExecutorService es = Executors.newCachedThreadPool();

        //向创建的线程池内提交任务
        for (int i = 0;i < 4; i ++){  //由于我们要求是四个窗口,所以需要提交四次任务,但是无论提交几次任务,线程池内创建线程的最大数依旧是4个
            es.submit(ticket);
        }

        //关闭线程池
        es.shutdown();
        //此处不推荐使用shutdownNow函数,因为会出现不可预知的错误

        //添加这个循环是为了,等到线程池的任务执行完成之后,在继续执行下面的那句输出
        //相当于是让主线程一直在空空的运行一个函数
        while (!es.isTerminated()){}

        System.out.println("程序结束了.....");

    }

执行结果每次执行都会不同,建议自己尝试,这里就不贴图了。

还有第三种,创建单线程线程池,比较简单,线程池内就一个线程,都是这一个线程来执行任务。

//1.创建固定大小线程池
ExecutorService es = Executors.newFixedThreadPool(4);
//2.创建缓冲线程池
ExecutorService es = Executors.newCachedThreadPool();
//3.创建单线程线程池
ExecutorService es = Executors.newSingleThreadExecutor();
1.5补充newScheduleThreadPool()调度线程池

两个功能:

  1. 周期执行:重复多次执行
  2. 延迟执行:只执行一次

对象的创建方式。

 ScheduledExecutorService es = Executors.newScheduledThreadPool(n);//指定线程个数
1.5.1 延迟执行schedule函数
 public static void main(String[] args) {

        ScheduledExecutorService es = Executors.newScheduledThreadPool(1);

        //1.延迟执行
        //三个参数,第一个是Runnable,第二个是延长多少时间,第三个是延长时间的单位
        //延期五秒之后打印:执行结束了
        es.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行结束了");
            }
        }, 5, TimeUnit.SECONDS);
        es.shutdown();
}

上述代码的意思是,延迟五秒之后打印:执行结束了,schedule函数的第三个参数可以指定时间单位,可以是毫秒、秒、天等等。

1.5.2固定周期执行scheduleAtFixedRate
 es.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                num++;
                System.out.println(num + "---" + Thread.currentThread().getName() + "....." + new Date().toLocaleString());
                if(num == 10){
                    es.shutdown();
                }
            }
        }, 0, 1, TimeUnit.SECONDS);

这个要手动设置shutdown的时机,而不是直接停止。第二个参数是初始执行要延迟的时间,如果要求第一次执行时马上开始,就设为0.
执行结果:那句“执行结束了”是延迟五秒执行(1.5.1)那个

1.5.3固定延迟执行scheduleWithFixedDelay

从代码上看,和固定周期执行的代码时完全一样的,先看一下代码,再说不同。

 es.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                num++;
                System.out.println(num + "---" + Thread.currentThread().getName() + "....." + new Date().toLocaleString());
                if(num == 10){
                    es.shutdown();
                }
            }
        }, 0, 1, TimeUnit.SECONDS);
1.5.4 固定周期执行和固定延迟执行的区别

打个比方:
你现在有一个任务需要10天去完成。
你按时打卡,每天完成当天的工作量。
但是第5天的时候,你摆烂了,休息了两天也就是第5天和第6天没干活。
这个时候,如果是 固定周期执行的话,那么你会在第7天补上前两天的,也就是这一天你会干5、6、7天的活,最后依旧是10天干完,
但是,如果是固定延迟执行,第7天的时候你依旧只干这一天的活,而整个任务,用12天去完成。

用代码来看一下。
首先是固定周期执行。

 es.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                num++;

                if (num == 5){
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(num + "---" + Thread.currentThread().getName() + "....." + new Date().toLocaleString());
                if(num == 10){
                    es.shutdown();
                }
            }
        }, 0, 1, TimeUnit.SECONDS);

执行结果:依旧是只用了十秒钟

如果是固定延迟执行

es.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                num++;
                if(num == 5){  //在第五天的时候休息两天
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(num + "---" + Thread.currentThread().getName() + "....." + new Date().toLocaleString());

                if(num == 10){
                    es.shutdown();
                }
            }
        }, 0, 1, TimeUnit.SECONDS);

执行结果:可以清楚的看到,程序的执行用了12秒

2.总结

线程池往往是用到实际开发中的,但是对于之前的线程的创建和使用,我们还是需要李姐的。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/874133.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-13
下一篇2022-05-13

发表评论

登录后才能评论

评论列表(0条)

    保存