并发学习--线程池

并发学习

并发工具类 - 分类
第一类:为了并发安全
第二类:管理线程 提高效率
第三类:线程协助
在这里插入图片描述

线程池的重要性

什么是池 ? 可以理解为计划经济 比如说我们的资源总量是有限的,就我电脑来说,我电脑有16个线程。所以,我就可以创建16个线程的线程池,我的任务可能很多,就依靠这16个线程来慢慢执行。然后,也不用创建的多,因为创建线程有多开销的。
所以:好处主要是有两个;第一个:可以复用我们的每一个资源
第二个:可以控制我们资源的总量
如果不用线程池,一个任务一个对应一个线程,那么1000个任务就需要创建1000个线程。对于java语言来说,一个线程对应操作系统的一个线程,这样的话,会给操作系统带来太大的开销。

为此:我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题。(如果用普通线程的话,执行完毕线程也就销毁了)
总结:线程池的好处
1.加快响应速度
2.合理利用cpu和内存
3.统一管理 (比如说:我不想执行刚刚那个任务了,如果我用线程就需要一个一个停,如果我使用线程池,停一次就好了)
线程池适合应用的场合
1.服务器接收到大量请求时,使用线程池技术是非常合适的,它可以大大减小线程的创建和销毁次数,提高服务器的工作效率。其实tomcat也是使用了线程池技术
2.在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。比如说,我们需要同时请求十个接口,并且对请求回来的结果做进一步处理,那么就很适合用线程池。

创建和停止线程池

在这里插入图片描述
线程池构造函数
在这里插入图片描述
在这里插入图片描述
比如说:corePoolSize = 5 此时,如果来了五个任务,系统就会创建5个线程;如果这5个任务结束了,线程池中会保存这5个任务,不会销毁。
maxPoolSize : 如果corePoolSize 核心线程都在处理,并且任务存储队列也已经满了,这时候它的扩展性就体现出来了。这时候,线程池会在核心线程基础下,进行扩展。最多会扩展到MaxPoolSize。
在这里插入图片描述
在这里插入图片描述
这里有个疑问?当队列满了maximumPoolSize没满的时候,创建的新线程是执行队列中的任务,还是最新的任务
keepAliveTime 存活时间
在这里插入图片描述
ThreadFactory:在线程池上,用来创建线程
在这里插入图片描述

线程池中默认创建线程可以找到,通过它可以查看是否守护线程,优先级等
在这里插入图片描述
workqueue:工作队列 还有,一个DelayedWorkQueue 这个是延时队列;
在这里插入图片描述
第一个是直接转换的,不能进行存储 个人理解就是 队列个数为0的有界队列

线程池应该手动创建还是自动创建

用Executors 自动创建 newFixedThreadPool 线程池测试:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewFixedThreadPoolTest {
    public static void main(String[] args) {
        //创建固定数为4的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        //增加1000个数量的任务
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}
class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印当前线程名
        System.out.println(Thread.currentThread().getName());
    }
}

结果如下:可以看到,一直是四个线程在执行
在这里插入图片描述
那么为什么会这样呢?进行源码分析:
在这里插入图片描述
结果显而易见:这个固定线程数就是传入的nThreads 最大线程数也是这个,最大存活时间为0 因为它最大线程数和固定线程数都一样的,不存在线程池中线程数会大于固定线程数的情况。TimeUnit.MILLSECONDS这个代表的是,最大存活时间的单位为微秒。后面是LinkedBlockingQueue 无界队列
在这里插入图片描述
下面演示OOM出错的情况:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewFixedThreadPoolTest {
    public static void main(String[] args) {
        //创建固定数为1的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        //希望它造成OOM,故加入的任务多一些
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(new Task());
        }
    }
}
class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印当前线程名
        System.out.println(Thread.currentThread().getName());
    }
}

在这里插入图片描述
oom错误如上;
这就是演示了用newFixedThreadPool可能出现的一种情况。所以,用固定数量的线程池还是容易发生错误的。
用Execotors创建SingleThreadExecutor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

Task任务写在上面:结果如下:
在这里插入图片描述
所以,只有一个线程,查看源代码:
在这里插入图片描述
在这里插入图片描述
这种线程池和刚刚的线程池差不多 只是把数量设置为1而已。也会导致同样的问题。
第三种线程池CachedThreadPool
可缓存线程池
特点:无界线程池,具有自动回收多余线程的功能

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }

    }
}

结果如下:生成了1000个线程
在这里插入图片描述
根据源码进行分析:
在这里插入图片描述
核心线程数为0,最大线程数为Integer.MAX_VALUE这个代表的数是非常大的。存活时间为60s,队列是SynchronousQueue这个代表的是无法存储,(不需要队列进行存储,直接进行创建线程)直接放入线程池,直接交换。
所以,这个队列的特点就可以说清楚了,无界线程池,可以缓存。
这种线程池的问题如下:
在这里插入图片描述
而且一直创建的话也会浪费资源,就刚刚的那个情况。

ScheduleAtFixedRate 线程的学习
特点:支持定时及周期性任务执行的线程池

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        //延迟5s后开始运行线程
        //scheduledExecutorService.schedule(new Task(),5, TimeUnit.SECONDS);
        
        //1s后每隔3s运行一次线程
        scheduledExecutorService.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
    }
}

经过以上的分析:创建线程池还是推荐手动创建比较好

下面 模拟一个创建线程池的方法:模拟队列的长度;并且设置拒绝策略为直接抛弃,不抛出错误;

package offer;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Study {
    public static void main(String[] args) {
        // 设置有界的队列
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(100);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, queue);
        // 设置拒绝策略,超过队列以后,不抛出错误,直接抛弃任务
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 200; i++) {
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(1);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

这样,就是超过了队列,也是不会抛出错误的;

线程池里线程数量设置多少比较合适:
在这里插入图片描述
在这里插入图片描述
第一个队列是无穷队列,第二个队列是空队列,不需要进行存储,第三个是延迟队列;
停止线程池的正确方法
1 shutdown:这个方法,会让线程池知道我们向让他停止任务,这时候,有新的任务时,它就不会再进行接收了。但是,他会将当前的还在执行的任务执行完。
isShutdown:判断线程池是否shutdown
isTerminated:判断线程池是否完全停止

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ShuntDown {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        //准备加入1000个任务
        for (int i = 0; i < 20; i++) {
            executorService.execute(new ShutDownTask());
            /*if(i == 10){
                executorService.shutdown();
            }*/
        }
        //已经加入1000个任务了,其他的都存在队列里面
        Thread.sleep(1500);
        //过了1.5s以后,准备关闭,这时候再有任务也不加了
        //判断是否shutdown
        System.out.println(executorService.isShutdown());
        //返回是否完全停止
        System.out.println(executorService.isTerminated());

        executorService.shutdown();
        //判断是否shutdown
        System.out.println(executorService.isShutdown());
        System.out.println(executorService.isTerminated());

        Thread.sleep(2000);
        System.out.println(executorService.isTerminated());
        //此时再加入就会报错
        executorService.execute(new ShutDownTask());

    }
}
class ShutDownTask implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结论如下:
在这里插入图片描述
awaitTermination:阻塞队列一段时间,看看队列是否执行完毕,如果执行完毕就返回true 如果时间过来队列还有线程在执行任务就返回false。一般用来做判断的。
在这里插入图片描述
shutdownNow:立刻关闭线程池,如果正在执行的任务,它们将会被中断,如果是队列中的任务,那么就直接返回,返回一个列表。
在这里插入图片描述
结论如下:在这里插入图片描述
注意:一般来说在队列中的任务要给其他线程池来进行重新执行的。
在这里插入图片描述
在这里插入图片描述
第一种策略是直接抛出异常,第二种是丢弃,第三种是丢弃老的那个,第四种是随给我提交的我丢弃给谁自己。第四种相对来说会好一点。
相关内容:可以查看以下链接拒绝策略
比如说:主线程提交的任务,他会再提交给主线程来进行运行;在这里插入图片描述
钩子方法,在每个任务执行前可以进行日志和统计

import jdk.nashorn.internal.ir.CallNode;

import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


/**
 * 这个方法演示每个任务执行前后都可以放钩子函数
 */
public class PauseableThreadPool extends ThreadPoolExecutor {
    private boolean isPaused = false;
    private final ReentrantLock lock = new ReentrantLock();
    private Condition unpaused = lock.newCondition();



    private void pause(){
        //lock.lock();

            isPaused = true;
            

    }
    public void resume(){
            lock.lock();
            try {
                isPaused = false;
                unpaused.signalAll();
            }finally {
                lock.unlock();
            }

    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        while(isPaused){
            try {
                unpaused.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
              lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        Runnable runnable = new  Runnable() {
            @Override
            public void run() {
                System.out.println("我被执行");
                try{
                    Thread.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 100; i++) {
            pauseableThreadPool.execute(runnable);
        }
        try {
            Thread.sleep(100);
            pauseableThreadPool.pause();
            System.out.println("线程池被暂停");

            Thread.sleep(100);
            pauseableThreadPool.resume();
            System.out.println("线程池重新开始执行");


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }
}

结果如下:在这里插入图片描述
这里还是有一点问题的,到时候回来再看看。
查看线程池源码
在这里插入图片描述
Executor:是线程池的最高级父接口,其内部只定义了一个execute方法
在这里插入图片描述
ExecutorService:继承Executor接口的接口;它有线程池基本上的方法;故Executors方法创建出线程池时,也是用它来进行接收的。
至于,ThreadPoolEcecutor它和ExecutorService一样就是线程池
在这里插入图片描述
比如说创建newCachedThreadPool就是调用他。

线程池实现线程任务复用的原理
点击进入ThreadPoolExecutor线程池中的runWorker执行方法,这个方法上面会描述他的作用

在这里插入图片描述
这里写着只要能得到任务就会一直执行。所以,线程池中会一直执行任务。
在这里插入图片描述
在这里插入图片描述
下面进行分析ThreadPoolExecutor方法中的execute方法
该方法会介绍他的流程
第一:如果线程池中运行的线程数少于线程池的核心数,那么就会增加。
第二:如果线程池中的线程数已经满了,都在跑就会增加到队列中
第三:如果线程池中队列满了,核心线程数也满了。那么就增加看看最大线程数有没有满,如果没满,可以增加返回true就可以增加(创建最大线程数来进行运行任务),如果满了返回false就拒绝任务了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值