并发编程多线程基础(草稿4)--线程池讲解

并发编程之线程池原理分析

第一节
  1. 并发队列(先进先出)
    (1)ConcurrentLinkedQueue(实体类) 非阻塞式队列
    (2)BlockingQueue(接口) 阻塞式队列
  • 阻塞:会进行等待 BlockingQueue

  • 非阻塞:不会进行等待

  • 阻塞式队列和非阻塞式队列的区别:

  1. 添加,出列的时候,如果超出了队列的总数,这时候阻塞式队列会进行等待,非阻塞式队列不会等待,报错或者报相关的提示信息,同理出队列和入队列一致

注意:
消费者在消息队列的时候,消费成功后,应将消费成功的消息清空掉。
非阻塞式的队列效率是高于阻塞式的队列。

第二节
  • 阻塞式队列最大的好处就是能够防止队列容器溢出,防止数据丢失。
  • ConcurrentLinkedQueue是并发包下面的,即java.util.concurrent下面的,该包下面的都是现成安全的。
  • offer表示向队列中放值,poll表示从队列中取值(默认是先进先出,只能取一个),且用完就删,但是peek不会删除,其作用也是消费队列。
  • poll方法:消费者消费成功后,自动将元素从队列中删除。
  • peek方法:消费者消费成功后,不会将元素从队列中删除。(不推荐)
  • concurrentLinkedQueue是一个无界队列。
  • 如果队列中已经没有数据了,再往出取值为null
重要

总结下BlockingQueue

  • 插入方法:

add(E e) : 添加成功返回true,失败抛IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则返回 false。
put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞

  • 删除方法:

remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若队列为空,则返回 null
take():获取并移除此队列头元素,若没有元素则一直阻塞。

  • 检查方法

element() :获取但不移除此队列的头元素,没有元素则抛异常
peek() :获取但不移除此队列的头;若队列为空,则返回 null。


总结下ConcurrentLinkedQueue(无界)
在这里插入图片描述


  1. 阻塞式队列和非阻塞式队列的用法
    非阻塞式队列:
package com.xiyou.mayi.thread5.bingfaQueue;

import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 阻塞式队列和非阻塞式队列的用法
 */
public class Test001 {
    public static void main(String[] args) {
        /**
         * 非阻塞式队列
         */
        // 生成非阻塞式队列,无界队列
        ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
        // offer表示添加队列
        concurrentLinkedQueue.offer("张三");
        concurrentLinkedQueue.offer("李四");
        concurrentLinkedQueue.offer("王五");
        // 获取队列,只能获取一个队列元素,先进先出
        System.out.println(concurrentLinkedQueue.poll());
        // 获取队列的个数, 上面从队列中取出一个值后,自动将其从队列中删除
        System.out.println(concurrentLinkedQueue.size());
        // peek方法不会将消费的数据从队列中删除
        System.out.println(concurrentLinkedQueue.peek());
        System.out.println(concurrentLinkedQueue.size());
        // 队列为空,则取出的值为null
        System.out.println(concurrentLinkedQueue.poll());
        System.out.println(concurrentLinkedQueue.poll());
        System.out.println(concurrentLinkedQueue.poll());
    }
}

输出结果:

张三
2
李四
2
李四
王五
null

阻塞式队列

  1. ArrayBlockingQueue:是一个有边界的阻塞式队列。内部实现是一个数组。有边界的意思是他的容量是有限的,我们必须在初始化的时候就指定他的容量。一旦确定就无法改变。
  2. LinkedBlockingQueue:阻塞队列大小的配置是可选的,如果我们初始化时指定了一个大小,他就是有边界的,如果不指定,他就是无边界的。说是无边界其实采用了默认的大小Integer.MAX_VALUE的容量,数量不足就扩容。他的内部实现是一个列表。
  3. PriorityBlockingQueue:是一个没有边界的队列,他的排序规则和PriorityQueue一样。注意,PriorityBlockingQueue中是允许插入null对象的。所有插入该队列中的对象必须实现Comparable接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的
  4. SynchronousQueue:该队列内部只允许容纳一个元素,当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费掉。

/**
 * 阻塞式队列的用法
 */
public class Test002 {
    public static void main(String[] args) throws InterruptedException {
        // 设定长度,表示是一个有界的队列。最大长度是3
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        // 添加非阻塞式队列的值,直接超过最大长度
        arrayBlockingQueue.offer("张三");
        arrayBlockingQueue.offer("李四");
        arrayBlockingQueue.offer("王麻子");
        // 队列长度是3,队列满了就不放数据,返回值是false,表示存入队列失败
        arrayBlockingQueue.offer("小五");
        // 如果队列满了等待3s放数据,如果队列满了就不放退出
        // 设置队列满的时候的等待时长
        arrayBlockingQueue.offer("阻塞式队列", 3, TimeUnit.SECONDS);
        for (String a : arrayBlockingQueue) {
            System.out.println(a);
        }
    }
}

结果:

(会等待3s,因为队列长度满了,等待3s队列还是满的,所以阻塞式队列的赋值由于设置了等待时长,所以先会等待,等待之后还是满的,所以存不进去)
张三
李四
王麻子

上面调用的是offer方法不会进行阻塞,只会返回true,false表示是否成功放入。(具体的上面讲到了)


第三节
  • 使用BlockingQueue来模拟生产者和消费者
  • 生产者线程负责添加队列,消费者线程负责消费队列
  • 生产者消费者都是用的同一个blockingQueue
  • volatile可以禁止重排序,保持线程的可见性
  • AutomicInteger是一个原子类,一般用该类进行i++操作,保证了线程的安全。
    (1)automicInteger.incrementAndGet(); // i++
  • LinkedBlockingQueue:是一个伪无界队列,如果不传入长度,则表示无界,实际上他也是有长度的,长度是Integer的最大值,如果长度超过该最大值,就会对其进行扩容、
  • 启动的时候应该先启动生产者,再启动消费者。
    concurrentLinkedQueue是Queue的一个安全实现,Queue实现的是先入先出,采用CAS操作 ,来保证元素的一致性;而BlockingQueue往前推用的就是synchronized关键字。
    代码用offer和poll实现,即用非阻塞的方式实现。:
package com.xiyou.mayiThread.myexecutors.mayiThread;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 主类
 */
public class ProfucerAndCusByQueue {
    public static void main(String[] args) {
        // 若不规定长度就是无界的,规定了就是有界的
        BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>(3);
        Producer producer = new Producer(blockingQueue);
        Consumer consumer = new Consumer(blockingQueue);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
        // 10s后停止
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 改变volatile的标志位,不再生产数据
        producer.stop();
    }
}

/**
 * 生产者
 **/
class Producer implements Runnable{

    // 声明队列,构造函数传入,生产者和消费者都是一个队列
    private BlockingQueue<String> blockingQueue;
    // 原子类,保证线程安全
    private AtomicInteger atomicInteger = new AtomicInteger();
    private volatile boolean FLAG = true;

    /**
     * 构造函数用来赋值队列
     * @param blockingQueue
     */
    public Producer(BlockingQueue<String> blockingQueue){
        this.blockingQueue = blockingQueue;
    }

    public void run() {
        System.out.println("生产者开始启动.....: " + Thread.currentThread().getName());
        while (FLAG) {
            // 待发送数据
            String data = atomicInteger.incrementAndGet() + "";
            try {
                // 向队列中存数据
                // 若向队列中添加成功,返回true否则返回false
                boolean offer = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
                if (offer) {
                    System.out.println(Thread.currentThread().getName() + " , 生产队列" + data + " 成功....");
                } else {
                    System.out.println(Thread.currentThread().getName() + " , 生产队列" + data + " 失败....");
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("生产者线程: " + Thread.currentThread().getName() + " 停止运行....");
    }

    public void stop(){
        FLAG = false;
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable{

    private volatile boolean FLAG = true;
    // 声明一个队列用来从里面消费数据,和生产者应该是同一个数据
    private BlockingQueue<String> blockingQueue;

    /**
     * 构造函数
     */
    public Consumer(BlockingQueue<String> blockingQueue){
        this.blockingQueue = blockingQueue;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + "消费者已经开始启动...");
        while (FLAG) {
            try {
                // poll取数据,若队列为空,取出数据为null
                // 2s内取出数据,无法取出返回null
                String data = blockingQueue.poll(2,TimeUnit.SECONDS);
                if (data == null) {
                    // 设置标志位,不再继续取数据
                    FLAG = false;
                    System.out.println("消费者超过2s未取出数据.....");
                    return;
                }
                System.out.println("消费者获取到队列的信息成功, data: " + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者: " + Thread.currentThread().getName() + " 已经结束....");
    }

}


  • 运行结果:
生产者开始启动.....: Thread-0
Thread-1消费者已经开始启动...
Thread-0 , 生产队列1 成功....
消费者获取到队列的信息成功, data: 1
Thread-0 , 生产队列2 成功....
消费者获取到队列的信息成功, data: 2
Thread-0 , 生产队列3 成功....
消费者获取到队列的信息成功, data: 3
Thread-0 , 生产队列4 成功....
消费者获取到队列的信息成功, data: 4
Thread-0 , 生产队列5 成功....
消费者获取到队列的信息成功, data: 5
Thread-0 , 生产队列6 成功....
消费者获取到队列的信息成功, data: 6
Thread-0 , 生产队列7 成功....
消费者获取到队列的信息成功, data: 7
Thread-0 , 生产队列8 成功....
消费者获取到队列的信息成功, data: 8
Thread-0 , 生产队列9 成功....
消费者获取到队列的信息成功, data: 9
Thread-0 , 生产队列10 成功....
消费者获取到队列的信息成功, data: 10
生产者线程: Thread-0 停止运行....
消费者超过2s未取出数据.....

Process finished with exit code 0

第四节 线程池的原理分析
  • 线程池的目的:
    (1)降低资源消耗,通过重复利用已经创建的线程,降低线程的创建和销毁造成的消耗。(创建线程比较费时间)
    (2)提高程序的效率,当任务达到的时候,任务可以不需要等待线程的创建就直接执行,因为线程池中的每一个线程并没有停止掉,无需重复进行创建。
    (3)方便管理,无限创建线程,不会出问题,底层有一个缓存队列,创建超出线程池容纳的最大数量的时候,会将其放到阻塞队列中,直至线程池有线程销毁,其可以继续创建
  • 线程池溢出:频繁的创建多线程,非常占用CPU内存,CPU来不及进行线程的切换,会引发线程池溢出的异常,创建的线程超过了可容纳的总数。
  • 如何配置合理的线程数
第五节 线程池的分类
  • 四种创建方式:Java通过Executors类(jdk1.5并发包)提供四种线程池
    (1)newCachedThreadPool:创建一个可缓存线程池,如果线程池的长度超过处理需求,可灵活回收空闲线程,若无可回收,则创建新的线程(不会规定初始的线程池的长度,自己创建,可以复用就复用,不能复用就新建。)其构造方法中有默认创建的最大线程数,最大线程数其实是无限大的。
    (2)newFixedThreadPool:创建一个定长线程池,可以控制线程最大的并发数,超出的线程会在阻塞队列中等待。该线程池规定了最大的线程数,只创建这么多的线程,其他的会等其运行完对其进行复用。底层使用BlockingQueue,超过了最大线程数会放到阻塞队列中,等有空余线程的时候,从队列中取任务复用线程执行。
    (3)newScheduleThreadPool:创建一个定长线程池,支持定时以及周期任务执行。定长的线程池,所以会传入具体的最大线程数。调用的时候调用.schedule方法,不用execute或者submit,因为只有schedule方法才可以定时执行。
    (4)newSingleThreadExecutor:创建一个单线程化的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。
  • Executors是一个具体的类。Executor是一个接口。
  • 线程池创建的方式:

Executors.newCachedThreadPool/newFixedThreadPool/newScheduleThreadPool/newSingleThreadExecutor()
创建四种线程池

举例说明:

  • execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口
  • submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口
  • 没有停止线程池,程序就会一直运行,调用.shutdown()就可以关闭线程池。
第六节 线程池的原理
  • 任何一个线程池的核心都是走的是ThreadPoolExecutor
/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize 核心线程数的数量
     * @param maximumPoolSize 最大线程的数量
     * @param 线程空闲超时的时间, (视频主角建议配置30s)
     * @param 超时时间的单位,分,秒等
     * @param 缓存队列,用于存放核心线程所执行的任务,核心线程执行该队列中的内容
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • corePoolSize(核心线程数)和maximumPoolSize(最大线程数)的区别是:(重要

核心线程数表示的是可以实际进行应用的线程数,表示当前线程池实际上会有多少个线程数进行使用。(最大可以运行的线程数)
最大线程数,表示线程池最大创建多少个线程。(最大可以创建的线程)

  • 过程:
    在这里插入图片描述
    (1)用户提交线程给线程池
    (2)如果当前线程池中的线程数目<=核心线程数,则每来一个任务,就会创建一个线程去执行这个任务;
    (3)如果当前线程池中的线程数目>=核心线程数,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲的线程将其取出去执行;若添加失败(一般情况下是任务缓存队列已经存满了),则会尝试创建新的线程去执行任务。(放到缓存队列中的线程,都是由核心线程去执行,不会重新拉起新的线程
    (4)如果队列已经满了,则在总线程数<=最大线程数的前提下,则创建新的线程,单独执行(队列已经满了表示当前线程执行的任务数够了,此时如果线程总数小于规定的最大的线程,那么我们就会新建一个线程执行下面的任务);如果当前线程池中的线程数目达到了maximumPoolSize(最大线程数)的时候 ,则会采取任务拒绝策略进行处理(就是说线程数大于最大线程数且阻塞缓存队列已经满了的时候,就无法新建了,直接报错);
    (5)如果线程池中的线程数量大于corePoolSize(核心线程数),如果某线程空闲时间超过了keepAliveTime,线程将被终止,直到线程池中的线程数目不大于corePoolSize(核心线程数);如果允许线程池中的核心线程设置存活时间,那么核心池中的线程空闲时间超过了keepAliveTime的时候,线程也会被终止、
第七节

自定义线程池:

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

/**
 * 自定义线程池
 */
public class ZiDingYiThreadPool {
    public static void main(String[] args) {

        // 所有的线程池其实就是调用了这个构造函数
        // 核心线程数,最大线程数,过期时长,时间单位,若超过核心线程将其保存到的阻塞队列
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
                2,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3));

        for (int i = 0; i < 5; i++) {
            TaskThread taskThread = new TaskThread("任务:" + i);
            // 向线程池中提交线程
            executor.execute(taskThread);
        }

        // 关闭线程池
        executor.shutdown();

    }
}

class TaskThread implements Runnable{

    private String taskName;

    public TaskThread(String taskName) {
        this.taskName = taskName;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + " : " + taskName);
    }
}
  • 运行结果:
pool-1-thread-1 : 任务:0
pool-1-thread-1 : 任务:1
pool-1-thread-1 : 任务:2
pool-1-thread-2 : 任务:4
pool-1-thread-1 : 任务:3

可以看到我们规定了其核心线程数是1,最大线程数是2,阻塞队列的长度是3,我们同时给线程池提交了5个任务,此时任务0进入被核心线程执行,任务1进入,此时核心线程已经使用,只能放到阻塞队列中,任务2,任务3同理,都是等待核心线程将其运行,此时任务4进入,此时核心线程数有用,队列已经满了,但是总线程只有一个是核心线程,但我们规定的是最大线程数是2,因此,我们会再新建一个线程去执行任务4.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值