第十八讲 线程池(极其重要的内容)

第十八讲 线程池(极其重要的内容)

1 线程池的概念

池:什么是池?池子,装水的。
为什么要有池子:蓄水,方便用水。想用就用,不用就放在那里备用。

线程池:池子里放的是线程,一条条已经开好的线程。
	具体的线程是Thread类型的对象。
	线程池就是将一个个new好的Thread类型的对象放在某个地方备用。
	对象放在哪里?堆内存中。
线程池就是:在堆中放一些已经new好的Thread类型的对象。供别人调用。
	这些对象,用完就还回去。相当于一个工具箱,用完之后把工具还回箱子中。

以前我们用线程的时候,哪里用哪里new,这样不是很好,不便于管理。
线程池就是提前准备好了线程,你用的时候只要去池子里面取就行了。
也就是说:
	我要用老虎钳子,以前的方式,要用的时候,我就买一把,用完丢掉。
	有了池的概念之后,就是我建了一个工具箱,箱子里面放了一把钳子
	每次用的时候我去箱子里面拿,用完还回到箱子中去。这样就不浪费了
	这种方式看上去是不是更加美好了。

优点:一次创建多次复用;便于管理;控制最大的并发数。
1. 降低资源消耗
2. 响应速度更快
3. 线程可管理了,可以统一监管。

2 java中线程池是怎么实现的

juc包中 Executor接口:执行器
Executor:
	An object that executes submitted Runnable tasks
	线程池是干什么的?存放线程的。线程是干什么的?处理并发任务的
	有多个Runnable任务,去线程池中拿线程来处理。
有了线程池我们不再需要new Thread(new RunnableImpl()).start();

看看Executor的架构:

在这里插入图片描述

找到了线程池,ThreadPoolExecutor 这是一个类。
怎样构造线程池呢?
public class PoolTest {

    public static void main(String[] args) {
        ExecutorService poolExecutor = Executors.newSingleThreadExecutor();
        //线程池被创建,且初始化了唯一一根线程
        for (int i = 0; i < 10; i++) {
            final int tem = i + 1;
            poolExecutor.execute(()->{
                //execute()方法到池子中拿一根线程执行lambda表达式中的任务
                System.out.println(Thread.currentThread().getName()
                                   + "\t 正在执行!" + tem);
            });
        }
    }
}

以上代码会有什么问题:线程一直执行,不结束。
原因:我们从池子中拿到这唯一的一根线程,让他执行任务,任务执行完毕
    但是没有还回去,就会认为这跟线程还有任务要执行,所以程序不会结束
线程池中的线程用完以后要关闭。
public class PoolTest {

    public static void main(String[] args) {
        ExecutorService poolExecutor = Executors.newSingleThreadExecutor();
        //线程池被创建,且初始化了唯一一根线程
        for (int i = 0; i < 10; i++) {
            final int tem = i + 1;
            poolExecutor.execute(()->{
                //execute()方法到池子中拿一根线程执行lambda表达式中的任务
                System.out.println(Thread.currentThread().getName()
                                   + "\t 正在执行!" + tem);
            });
        }
        poolExecutor.shutdown();
    }
}
上述代码还有什么问题吗?
public class PoolTest {

    public static void main(String[] args) {
        ExecutorService poolExecutor = Executors.newSingleThreadExecutor();
        //线程池被创建,且初始化了唯一一根线程
        for (int i = 0; i < 10; i++) {
            final int tem = i + 1;
            poolExecutor.execute(()->{
                //execute()方法到池子中拿一根线程执行lambda表达式中的任务
                System.out.println(Thread.currentThread().getName() 
                                   + "\t 正在执行!" + tem);
            });
            Object object = null;
            object.hashCode();
        }
        poolExecutor.shutdown();
    }
}   
// 执行结果:
Exception in thread "main" java.lang.NullPointerException
	at com.tj.ThreadPool.PoolTest.main(PoolTest.java:24)
pool-1-thread-1	 正在执行!1
程序仍然不会结束。
    因为出现异常以后,后续的代码不会被执行,但是线程没有被释放
    所以程序不会结束。
    老师,出现空指针以后,main方法结束了。为什么这里不结束?
    因为这里开启了另外一个线程,也就是有了另外一个栈空间
    main方法确实结束了,主线程结束,栈销毁,但是线程池中的线程
    正在执行,它所在的栈空间没有收到销毁的指令shutdown()
    还回线程的本质是让它所在的栈空间销毁。

怎么解决上述问题:
    以前,我们使用到锁的时候,trylock,因为,锁用完以后要释放
    以前,我们使用IO,打开流以后,要关闭。
    我们所知道的所有的关于关闭的方法都应该在finally中被调用
    因为,不管发生什么,我都要确保它被关闭。这样才能不占资源
    才能让程序正确执行。
 public class PoolTest {

    public static void main(String[] args) {
        ExecutorService poolExecutor = Executors.newSingleThreadExecutor();
        try {
            for (int i = 0; i < 10; i++) {
                final int tem = i + 1;
                poolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName() 
                                       + "\t 正在执行!" + tem);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }
    }
}


public class PoolTest {

    public static void main(String[] args) {
//        ExecutorService poolExecutor = Executors.newSingleThreadExecutor();
        //线程池被创建,且初始化了唯一一根线程

//        ExecutorService poolExecutor = Executors.newFixedThreadPool(3);
        // 固定线程数
        ExecutorService poolExecutor = Executors.newCachedThreadPool();
        //数量不固定,可伸缩
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                final int tem = i + 1;
                poolExecutor.execute(()->{
                    //execute()方法到池子中拿一根线程执行lambda表达式中的任务
                    System.out.println(Thread.currentThread().getName()
                                       + "\t 正在执行!" + tem);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }
    }
}


以上:如果别人问你,实际开发中使用哪种方式创建线程池,你如何回答?都不用!
    怎样创建线程池呢?自己定义。自己创建。

阿里java开发手册:第154. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

3 线程池的底层原理

线程池的核心:ThreadPoolExecutor类
1. public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)
关于ThreadPoolExecutor类的构造方法参数:7个参数
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
对于7个参数的解析:
    int corePoolSize:核心池的大小,池子的大小怎么衡量?线程数
    int maximumPoolSize:池子最大的大小:最多能容纳多少条线程
    long keepAliveTime:保持活跃时间,谁活跃?线程
    TimeUnit unit:时间单位
    BlockingQueue<Runnable> workQueue:阻塞队列,这个队列放什么玩意?
    ThreadFactory threadFactory:线程工厂,干什么的?生产线程
    RejectedExecutionHandler handler:拒绝策略
以上七个参数,告诉了我们整个故事的真相:
前两个参数:
    int corePoolSize:主要干活的线程有多少条?也就是一直在线的线程数。
    int maximumPoolSize:池子最多能容纳的线程数
通过这两个参数我们可以想到这样一个故事:
    线程池它创建出来之后,并不是把所有的线程都激活,
    一出来就干活的线程有corePoolSize个;
    如果有更多的任务进来,corePoolSize个线程忙不过来的时候,
    就会激活(maximumPoolSize-corePoolSize)个线程,让他们来帮忙干活。
第三四个参数:
    long keepAliveTime:线程活跃。谁?core以外的线程。
    	一旦被激活,完事之后,会有一个活跃时间。然后就会还回去。
    TimeUnit unit:时间单位
第五个参数:
    BlockingQueue<Runnable> workQueue:阻塞队列
    为什么要有阻塞队列?阻塞什么东西?阻塞队列中放的是什么?
    线程池放的什么?线程
    放线程干什么?处理并发任务。
    BlockingQueue<Runnable>:这个是一个容器,放什么?并发任务。
        他有容量吗?有
第六个参数:
    ThreadFactory threadFactory:线程工厂,这个工厂生产线程。
        这个工厂一次性生产maximumPoolSize个线程。
        工厂中有个容器放线程。具体使用哪条创建好的线程,直接去工厂拿就行了。
        工厂就是那个池子。
        但是线程池(ThreadPoolExecutor)不是仅仅只是池子,
        还有线程和并发任务以及处理机制。
        
第七个参数:
     RejectedExecutionHandler handler:拒绝执行处理器。
        什么意思?如果,容量爆了,还要接任务吗?
        阻塞队列是不是一边添加任务,一边有线程来拿任务处理。
        当这个队列满了,而且线程都忙不过来的时候,这时候不能再接了。
        那么就会有处理机制来拒绝这些任务。
举个例子:
	学校的校车车站,你们常常看到有几辆车在那里等着?2辆车(core)
	实际学校的校车最多有5辆。(max)
	这些校车都有校车工厂生产。factory
	日常坐车人数不多,2辆车就够了。
	当新生入学第一天,要去车站接人,
	来的人太多了,2辆车不够用了。
	但是,我不知道接下来来多少人,
	我要把我最大的承载能力拿出来,所以5辆车都会开出
	这时候,剩下的3辆车都要出动。
	当开学第二天,人数不多了,2辆车够了,
	那么增加的3辆车等了一个上午,发现还是没事干。
	这三辆车从车站驶离,回去休息。
	
	如果,五辆车都在车站忙不过来,到来的人应该在学校划定的区域排队等待。
	先来先上车,后来后上车,这就是阻塞队列。
	如果,队列都满了,而且车也在路上,还有源源不断的人来。
	怎么办呢?你们不要坐校车,自己想办法去。坐公交、打的、走路。
	这时候,等待区有工作人员竖起了牌子,请后来的同学和家长,自行坐车到校门口去
	这就是拒绝机制。RejectedExecutionHandler。
	1)学校不处理就乱套了,抛异常
	2)自行处理,自己走路、自己打车、自己坐公交
	3)学校帮忙找了公交车、的士,交给别人去处理...
	
线程吃的执行逻辑

在这里插入图片描述

在这里插入图片描述

4 自定义线程池

public class DefineThreadPool {

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2, 5, 60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(),
                //new ThreadPoolExecutor.DiscardOldestPolicy()
                // new ThreadPoolExecutor.DiscardPolicy()// 这不负责任,只执行8个,剩下的92个抛弃掉
                // 这玩意丢数据。如果允许丢数据用它是可以的。
                new ThreadPoolExecutor.CallerRunsPolicy()//交由调用该pool的线程来执行,常用用这个
                //也可以自定义
                //new ThreadPoolExecutor.AbortPolicy() 中止,JVM抛出异常
        );

        try {
            for (int i = 1; i <= 100; i++) {
                final int tem = i;
                pool.execute(()->{
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在执行!" + tem);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}

5 怎样确定线程池大小是合适的呢

根据任务的类型来的,任务一般分为两种:
IO密集型:IO次数较多
CPU密集型:主要占用的CPU的资源
如果是CPU密集型,一般线程池的大小为CPU核数或者是核数+1 +2
如果是IO密集型,大小一般是核数/阻塞系数。
怎样获得CPU核数?
int r = Runtime.getRuntime().availableProcessors();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值