多线程(2)——阻塞式队列、线程池

一、阻塞式队列

1.背景

(1)含义

阻塞式队列:简单的讲就是给两个线程之前加了一个类似缓冲区的一个队列。保证两个线程在相互作用的时候没有耦合关系。

(2)形象解释

现在有一个面包店,它所进行的任务就是生产面包(面包师傅生产面包就相当于是一个线程)。现在还有一群消费者,它们的任务就是消费面包(消费者就是另一个线程)。在实际操作过程中,面包店不会直接和消费者之间进行沟通。他们会将生产的面包放在仓库里边,然后有专业的销售人员(相当于是上边说的临时的缓冲区)去和顾客进行沟通。


(3)用图来表示
在这里插入图片描述

2.作用

(1)解耦

1)含义:表示的意思就是要是线程A和线程B直接交流,他们之间就是强耦合关系。如果在A、B之间加入了中间件,就是线程A和线程B之间间接的交流,让A和中间件交流,再让中间件和B交流,这样就能让A和B交流了。
2)形象理解:面包师傅直接和销售交流,销售和消费者交流。面包师傅和消费者之间没有强的关系性。
3)解耦的作用:利于程序代码更好的扩展,让自己只干自己的事情。形象理解就是:面包师傅不用关心消费状态,只负责自己生产面包,这样就省心了好多。

(2)削峰

1)含义: 将线程A最高峰的信息先放在一个队列里边,这样一来就间接的避开了A的最高峰。
2)形象理解:面包师傅生产面包的速度太快,消费者消费面包的速度慢,因此将面包师傅生产的面包先放在一个队列里边。消费者就能按照自己的消费速度消费面包,避开了生产面包的最高峰。
3削峰的作用:防止一个线程生产较快,一个线程消费较慢的情景。

3.实现方式

(1)实质

只要能循环的方东西就行,循环数组、循环链表都能够实现。只不过原来我们学的循环队列里边放的是数字等等,现在给里边放的是一个个任务。下边我们采用循环数组的方式进行实现。

(2)代码

public class MyBlockingQueue<E> {

    // put表示从队列里边拿东西,take表示给队列里边放东西。
    private Object[] elements;
    private int putIndex;//存放元素的索引
    private int takeIndex;//取元素的索引
    private int size;//存放元素的数量

    public MyBlockingQueue(int capacity){
        elements = new Object[capacity];
    }

    //线程安全的存放元素:如果超过最大容量,需要等待,否则就存放
    public synchronized void put(E e) throws InterruptedException {
        while(size==elements.length)
            wait();
        elements[putIndex] = e;
        // 下边的操作是为了,要是取到队列的最后一个元素的时候,就要循环到首位,所以模数组的长度。
        putIndex = (putIndex+1) % elements.length;
        size++;
        notifyAll();
    }

    //线程安全的取元素:如果队列中没有元素,需要等待,否则就可以取
    public synchronized E take() throws InterruptedException {
        while (size==0)
            wait();
        E e = (E) elements[takeIndex];
        takeIndex = (takeIndex+1) % elements.length;
        size--;
        notifyAll();
        return e;
    }
    }

注意:要实现的是循环,所以到达队列的末端的时候,要先办法从队列的头部在进入,所以上边的代码要模上队列的长度。

二、线程池

1.背景

在实际中,创建线程的时候,Java创建的是系统级别的线程,这种线程是基于轻量级进程创建的,在创建爱你的时候比较耗时,那么能不能创建一个专门存放线程的池子,里边就是创建好的线程,在提交具体任务的时候不用反复的创建线程,而是重复利用已经创建好的线程。让线程池里边你的线程来执行这些任务,进而降低线程在创建和销毁时对资源的消耗。

2.作用

1)能反复利用线程池里以创建好的线程,降低线程创建对资源的消耗:在线程创建的时候是基于轻量级进程实现的,要是不断的创建线程势必会要反复的创建,创建完成之后还要进行销毁。在这个过程中,是很消耗资源的。所以直接弄个专门放线程的池子,能反复的利用里边的线程,巧妙的解决了这些问题。
2)能提高响应任务的速度:在任务来的时候,原来是要创建线程,创建好线程之后才能执行具体的任务。那么在创建线程时任务还是要等待,只有创建好之后才能执行,这种响应速度会很慢。但是在用线程池之后,池子里边有自己以创建好的线程,任务来了之后能直接执行,不用当下创建,相应任务的速度就很快。
3)能更好的管理线程::线程是在进程下创建的,进程是要系统分配资源的,由于系统的资源是有限的。因此进程和线程都是稀缺资源,不能无终止的创建线程。创建线程池,之后就减慢了线程的重复创建过程,让创建好的线程在一个池子里,更好的管理线程。

3.线程池的构建方式

(1)快捷方式创建线程池

主要有以下四种用快捷方式创建线程池的方法,但是实际的用处不是很大。因为他们几个里边没有指定创建线程的工厂类,还有具体的拒绝策略。持续创建势必会消耗完内存,下边只是展示一下这四个快捷方式。

        // 1.单线程池:只有一个线程运行任务
        ExecutorService single = Executors.newSingleThreadExecutor();
        // 2.固定大小的线程池:固定数量的线程执行任务
        ExecutorService fixed = Executors.newFixedThreadPool(4);
        // 3.缓存的线程池
        ExecutorService cached = Executors.newCachedThreadPool();
        // 4.定时任务的线程池
        ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(4);

(2)指定参数创建线程池(重点

(1)方法的样子,及里边的参数

// 这是源码,可以看出里边有7个参数,分别是括号里边的7个参数。
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

(2)7个参数的含义
下边为了帮助理解里边的参数,要开一个快递公司来对应举例子。

1)corePoolSize:表示的是核心线程数,创建的线程池里边的核心线程数量。 形象理解:快递公司的开设就要招员工,核心线程对应正式员工

2)int maximumPoolSize:表示的是最大线程数量,线程池里边不但有和兴线程数还有临时线程数,核心线程数 + 临时线程数 = 最大线程数。形象理解:开快递公司时,在快递高峰期会加派人手,所以会招临时员工。临时员工 + 正式员工 = 最大数量的员工。最大线程数对应招聘的临时工和正式工之和

3) long keepAliveTime:闲置线程的超时等待时间,要是超过这个时间,非核心线程就要被回收。
形象理解:临时工要是一定的时间没有活干,就要被解雇。临时线程超时等待时间对应解雇临时工的条件

4)TimeUnit unit:闲置线程超时等待时间的单位。解雇时间单位。

5 ) BlockingQueue:阻塞式队列,里边放的是超过核心线程以外的被提交的任务。
形象理解:快递公司除了临时工和正式工之外肯定有自己的仓库,将处理不过来的快递放在仓库里边。仓库对应阻塞式队列

6)ThreadFactory :线程池的创建策略,表示线程池是遵循怎样的要求创建出来的。 形象理解:建工厂时要遵循的规定设计。创建工厂的规定对应创建线程池的规定

7)RejectedExecutionHandler:拒绝策略,要是等到达到线程的最大数量之后。还有任务提交过来就要拒绝提交的任务形象理解:要是正式工、临时工满负荷运载,并且此时仓库也存满了。要是现在还有包裹存入,就要拒绝。

拒绝策略哟分为下边四种:
new ThreadPoolExecutor.CallerRunsPolicy() // 表示将提交的任务交给提交的人让它自己租处理。
new ThreadPoolExecutor.AbortPolicy() // 表示用抛异常的方式来拒绝执行提交的任务。
new ThreadPoolExecutor.DiscardPolicy() // 表示,直接把提交的任务抛弃掉.
new ThreadPoolExecutor.DiscardOldestPolicy() //表示丢弃掉时间最久的任务,并执行当前的任务。

(3)两个创建线程池方法的区别

最大的区别就是:快捷方式创建线程时,没有创建工厂类和拒绝策略。这也是为啥实际不用快捷方式创建线程池,实际开发中,要是提交任务的速度比执行任务的速度快,长期以来必然会导致线程池满掉,要是没有拒绝策略,就会导致进程里边的内存满掉,进而导致内存溢出,进程就会挂掉。

4.线程池在执行任务的工作流程

这里用语言不好描述,下边画了一张图,请仔细阅读,里边的每一步都有详细解说:
在这里插入图片描述

5.如何停止线程池

实质是:通过调用shutDown()和shutDownNow()来进行停止。实质还是调用interrupt方法来改变中断标志位,进而让里边的每一个线程关闭

注意:

1) shutDown():表示的是中断阻塞队列里边的任务,但是正在执行的任务还是会继续执行
2)shutDownNow():表示的是中断所有的线程

好了今天我们主要复习了多线程中的线程池和阻塞式队列的问题,希望能给大家带来帮助。加油哈,未来可期!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值