从线程池到阻塞队列

为什么要用线程池

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
  • 方便管理线程:线程是稀缺资源,如果无条件地创建,不仅会消耗资源,还会降低线程的稳定性,使用线程池可以统一分配、调优和监考。

线程池的核心参数

创建线程池的方法:

  • corePoolSize:核心线程的数量
  • maximumPoolSize:线程池能创建的最大线程个数
  • keepAliveTime:空闲线程存活时间
  • unit:时间单位
  • workQueue:用于保存任务的阻塞队列
  • threadFactory:创建线程的工程类
  • hadler:饱和策略

常见线程池的区别以及特点

newCachedThreadPool:

  • 特点:newCachedThreadPool创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要,它可以灵活的回收空闲的线程,当需要添加的时候可以灵活的添加
  • 缺点:maximumPoolSize被设置为Inter.MAX_VALUE,可能会造成OOM

newFixedThreadPool:

  • 特点:创建一个定长的线程池,可控制线程最大并发数,超出的任务会在线程中等待。
  • 缺点:线程数量是固定的,但是阻塞队列是LinkedBlockingQueue,是无界队列,也可能会造成OOM

newScheduledThreadPool:

  • 特点:创建一个固定长度的线程,而且支持定时的以及周期性的任务执行,类似Timer
  • 缺点:底层封装了PriorityQueue,同样是无界队列,可能会造成OOM

newSingleThreadExecutor:

  • 特点:单线程化的线程池,它会用唯一的工作线程来执行任务。如果这个线程因为异常结束,那么会有一个新的线程来替代它。它必须保证前一项任务完成才能执行后一项。阻塞队列是LinkedBlockingQueue,因此是无界队列,会有OOM的风险
  • 缺点:因为是单线程,高并发下有压力

为什么我们不用Executors默认创建线程池的方法,而直接自己手动去调用ThreadPoolExecutor去创建线程池

Executors 返回的线程池对象的弊端如下:

  1. newFixedThreadPool newSingleThreadPool: LinkedBlockingQueue无界队列,允许的请求队列长度为 Integer.MAX_VALUE(无界队列),可能会堆积大量的请求,从而导致 OOM。
  2. newCachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 
  3. newScheduledThreadPool:同样使用无界队列(底层是PriorityQueue),也会堆积大量请求导致OOM

线程池的饱和策略有哪些 

  • ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException来拒绝任务的处理
  • ThreadPoolExecutor.CallerRunsPolicy:调用提交任务的线程运行任务(比如A提交线程,A运行任务)。但是会降低新任务提交速度,影响程序的整体性能。
  • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃掉最早的未处理的任务

线程池执行原理

  1. 判断线程池的核心线程数是不是已满,如果不是则创建一个新的工作线程来执行任务。
  2. 如果核心线程数已满,则将提交的任务放在保存任务的阻塞队列中。
  3. 如果工作任务队列满了,则创建一个新的线程来执行任务,直到数量到达maximumPoolSize
  4. 最后如果达到线程池最大线程数,则采取对应的饱和策略

线程池中execute()和 submit()方法有什么区别

相同点:

  • 都可以提交任务到线程池中

不同点

  • 接受参数:execute只能执行Runnable类型的任务,submit可以执行Runnable和Callable类型的任务
  • 返回值:submit方法可以返回持有计算结果的Future对象,而execute没有
  • 异常处理:submit可以方便处理异常

Java中Executor、Executors和ExecuteService的区别

  • Executor是最基本的接口,只定义了一个execute方法
  • ExecuteService是一个高级的接口,实现了Executor并进行了扩展,比如实现了submit方法。这个接口的目的是方便我们使用底层不同的线程池,类似List接口,屏蔽底层差异。
  • Executors是一个工具类,使用这个工具类可以方便的创建线程。让我们可以不用手动地指定线程池的各个参数,比如Executors.newFixedThreadPool(10);

线程池有哪些状态

  • Running:正常状态,可以接受其他线程
  • Shutdown:不接受新的任务提交,但是会继续处理等待队列中的任务
  • Stop:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程
  • Tidying:所有的任务都销毁,workerCount(线程数量)为0,线程池在向Tidying状态转换时,会执行钩子方法terminated()
  • Terminated:terminated()方法介绍后,就会变成这个

如何合理分配线程池大小

  • CPU密集型,任务可以少配置数,大概和CPU核数相当,这样可以使得每个线程在执行任务
  • IO密集型,大部分线程在阻塞,故需要多配置线程数,2 * cpu核数

线程池如何实现动态修改

线程池提供了部分setter方法可以设置线程池的参数:

  • 修改线程数,最大线程数,空闲线程停留时间,拒绝策略等
  • 可以将线程池的配置参数放入配置中心,然后直接在配置中心修改

什么时候需要修改?

  1. 需要监考报警策略,获取线程池状态指标,当指标判定为异常后再报警
  2.  分析指标原因,评估策略,然后通过上述线程池提供的接口进行修改

既然线程池中使用了阻塞队列,那么什么是阻塞队列,阻塞队列有哪些

阻塞队列支持两个阻塞的插入和删除操作

  • 支持阻塞的插入put方法:当队列满的时候,队列会阻塞插入元素的线程,直到队列不满
  • 支持阻塞的移除take方法:当队列为空的时候,队列会阻塞移除元素的线程,直到队列不为空

阻塞队列:

  • ArrayBlockingQueue:底层使用数组结构,创建时必须指定大小,是有界的
  • LinkedBlockingQueue:底层使用链表结构,创建时默认大小是Inter.MAX_VALUE,因此是无界的。也可以指定大小成为有界
  • PriorityBlockingQueue:一个支持优先级排列的队列,可重写自定义类的compareTo方法来指定排序规则
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,表示指定多久才能从队列中获得元素
  • SynchronousQueue:一个不存储元素的队列,每一次put必须等待一个take操作,否则不能添加元素。适用于传递性场景
  • LinkedTransferQueue:一个由链表结构组成的无界队列,设计了一种生产者和消费者之间传递的机制,称为”transfer“。当生产者调用transfer(e)方法时,它会阻塞直到一个消费者接收该元素。
  • LinkedBlockingDeque:一个由链表结构组成的双端队列

ArrayBlockingQueue和LinkedBlockingQueue的区别

  • 底层实现:ArrayBlockingQueue基于数组,LinkedBlockingQueue基于链表
  • 是否有界:ArrayBlockingQueue有界,LinkedBlockingQueue创建时可以指定大小,默认是Integer.MAX_VALUE,无界
  • 锁是否分离:ArrayBlockingQueue中的锁不分离,生产者和消费者使用同一把锁。LinkedBlockingQueue的锁分离,生产者使用的是putLock,消费者使用的是takeLock,这样可以防止生产者和消费者之间竞争锁
  • 内存占用:ArrayBlockingQueue需要提前分配内存,LinkedBlockingQueue是动态分配内存,会不断占用空间

ArrayBlockingQueue底层源码

属性

    final Object[] items;  //队列的底层为数组,是个循环数组
    int takeIndex;  //从队列中取元素的索引,用于take、poll、remove
    int putIndex;  //向队列中存放元素的索引,用于put、offer、add
    int count;  //队列中的元素数
    final ReentrantLock lock; //队列中的锁机制,可重入锁
    private final Condition notEmpty;   //notEmpty条件对象,由lock创建
    private final Condition notFull;  //notFull条件对象,由lock创建
    transient Itrs itrs = null;  //迭代器对象

添加方法

add方法

/*调用了offer(e)方法,成功,返回true,失败,抛出IllegalStateException异常*/
 public boolean add(E e) {
        if (offer(e)) 
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

enqueue(e)方法的实现:

/**
    * 在当前put位置插入元素、前进和信号
    * Call only when holding lock. 只有在持有锁资源时才调用该方法
    */
   private void enqueue(E x) {
       // assert lock.getHoldCount() == 1;
       // assert items[putIndex] == null;
       final Object[] items = this.items; //将队列数组初始化
       items[putIndex] = x;  //将元素添加到数组里
       if (++putIndex == items.length)  //如果将要插入的元素索引等于数组的长度,将存放元素的索引重新置为0
           putIndex = 0;
       count++;
       notEmpty.signal();   //使用条件对象notEmpty通知,唤醒当前等待的线程
   }

checkNotNull(Object obj)方法的实现:

/**
     * Throws NullPointerException if argument is null.
     *如果参数为null,则抛出NullPointerException的异常
     * @param v the element
     */
    private static void checkNotNull(Object v) {
        if (v == null)
            throw new NullPointerException();
    }

put方法

 /**
     * 将指定的元素插入到此队列的末尾,然后等待
     * for space to become available if the queue is full.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        checkNotNull(e); //判断元素是否为null
        final ReentrantLock lock = this.lock;  //初始化重入锁
        lock.lockInterruptibly(); //加锁,以保证在调用put方法时只有一个线程
        try {
            while (count == items.length) //当队列满了,阻塞当前线程,并加入到条件对象notFull的等待队列里面
                notFull.await(); //线程阻塞并被挂起,同时释放锁资源
            enqueue(e); //调用enqueue方法
        } finally {
            lock.unlock();  //释放锁,让其他线程可以调用put方法
        }
    }

offer实现(添加方法的具体实现)

 public boolean offer(E e) {
        checkNotNull(e); //检查队列中的元素是否为空。在这里不允许为空
        final ReentrantLock lock = this.lock; //引入重入锁
        lock.lock(); //加锁,保证调用offer时只有一个线程
        try {
            if (count == items.length)  //如果当前元素的个数等于队列数组的长度,说明队列是满的,添加失败
                return false;
            else {//否则队列不满,调用enqueue(e)方法添加元素,返回true
                enqueue(e);
                return true;
            }
        } finally {//最后,释放锁,让其他线程可以调用offer方法
            lock.unlock();
        }
    }

删除方法

poll方法

 public E poll() {
        final ReentrantLock lock = this.lock; //引入重用锁
        lock.lock();  //加锁,以保证当前只有一个线程
        try {//如果队列为空,则返回null;否则,调用dequeue方法
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock(); //释放锁资源,让其他线程可以调用poll方法
        }
    }

take方法

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();//加锁,以保证在调用take()方法时只有一个线程
        try {
            while (count == 0) //当队列中元素个数为1,即队列为空时 
                notEmpty.await(); //阻塞当前线程,并加入到条件对象notEmpty的等待队列里
            return dequeue(); //调用dequeue()方法
        } finally {
            lock.unlock(); //释放锁,让其他线程可以调用take()方法
        }
    }

remove(Object obj)方法

/* 从队列中删除指定的元素。如果该元素存在,则将该元素从队列中删除,返回true;如果不存在,则返回false
*/
    public boolean remove(Object o) {
        if (o == null) return false;//如果指定删除的元素为null,则返回false
        final Object[] items = this.items; //阻塞队列数组
        final ReentrantLock lock = this.lock; //重入锁
        lock.lock(); //加锁,以此保证在调用该remove方法时只有一个线程
        try {
            if (count > 0) {//如果队列不为空
                final int putIndex = this.putIndex; //往队列中即将要存储的元素的下标
                int i = takeIndex; //从队列即将要取出元素的下标
//循环遍历阻塞队列中的元素,如果在队列中找到了要删除的元素,则将该元素删除,返回true;否则,返回false。
                do {
                    if (o.equals(items[i])) { //
                        removeAt(i);
                        return true;
                    }
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);//结束条件为当前元素索引==最后将要存入队列中的元素的下标
            }
            return false;
        } finally {
            lock.unlock();//释放锁资源,让其他线程可以调用remove(e)方法
        }
    }

deque方法(以上方法中使用过):

  /**
     * Extracts element at current take position, advances, and signals.提取元素当前的位置、进展和信号
     * Call only when holding lock.在持有锁时才调用
     */
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;//阻塞队列数组
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];//用变量x记录当前要取出的元素
        items[takeIndex] = null;//将该元素置为null
        if (++takeIndex == items.length)//判断是否是最后一个元素
            takeIndex = 0; //如果是,将取元素索引置为0,从头开始取
        count--;//元素个数-1
        if (itrs != null) //迭代遍历队列,
            itrs.elementDequeued();
        notFull.signal();// 使用条件对象notFull通知,比如使用put方法放数据的时候队列已满,被阻塞。这个时候消费了一条数据,队列没满了,就需要调用signal进行通知  
        return x;
    }

LinkedBlockingQueue底层源码

属性

static class Node<E> {
        E item;  //元素
        Node<E> next;//next指针
        
        Node(E x) {  //有参构造函数
          item = x;
          }
          
    private final int capacity;  //容量,默认为 Integer.MAX_VALUE
    private final AtomicInteger count = new AtomicInteger();  //队列中元素的数量
    transient Node<E> head;  //头节点
    private transient Node<E> last;  //尾节点
    private final ReentrantLock takeLock = new ReentrantLock(); //拿锁
    private final Condition notEmpty = takeLock.newCondition();  //拿锁的条件,队列不为空
    private final ReentrantLock putLock = new ReentrantLock();  //放锁
    private final Condition notFull = putLock.newCondition();  //放锁的条件
    }

添加方法

add方法

    public boolean add(E e) {
        addLast(e);
        return true;
    }

put方法

public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();//判断添加的元素是否为null,如果为Null,抛出NullPointerException异常
        int c = -1; 
        Node<E> node = new Node<E>(e); //构造新的结点
        final ReentrantLock putLock = this.putLock; //放锁
        final AtomicInteger count = this.count; //元素的个数
        putLock.lockInterruptibly(); //放锁加锁,保证在调用put方法的时候只有1个线程 
        try {
            while (count.get() == capacity) {//如果队列为满
                notFull.await();//阻塞并挂起当前线程
            }
            enqueue(node);//将元素添加到链表的尾部
            c = count.getAndIncrement(); //元素个数+1
            if (c + 1 < capacity) //如果队列的容量还没有满
                notFull.signal(); //在notFull对象上唤醒正在等待的1个线程,表示队列中还有元素可以消费
        } finally {
            putLock.unlock(); //释放放锁,让其他线程可以调用该put方法
        }
        if (c == 0)//由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据 
            signalNotEmpty();//在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费 
    }

enqueue(上面方法用到)实现: 

 private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }

offer方法(添加方法的具体实现,分为offerFirst和OfferLast)

    public boolean offerFirst(E e) {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return linkFirst(node);
        } finally {
            lock.unlock();
        }
    }

    public boolean offerLast(E e) {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return linkLast(node);
        } finally {
            lock.unlock();
        }
    }

删除方法

poll方法

  public E poll() {
        final AtomicInteger count = this.count; //队列中元素的个数
        if (count.get() == 0) //判断该队列是否为空
            return null; //如果为空,返回null
        E x = null; //定义要返回的元素的变量名,初始化为Null
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;//拿锁
        takeLock.lock();//拿锁加锁,以保证在调用poll()线程的时候只有1个线程
        try {
            if (count.get() > 0) {//判断队列是否为空。如果不为空
                x = dequeue();//删除头节点
                c = count.getAndDecrement();//元素个数-1
                if (c > 1)//如果队列中还有元素
                    notEmpty.signal();//在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费 
            }
        } finally {
            takeLock.unlock();//释放拿锁资源,让其他线程可以调用该poll()方法
        }
        if (c == capacity)//由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据 
            signalNotFull();//在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据 
        return x;//返回删除的元素
    }

take方法

public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count; //队列中元素的个数
        final ReentrantLock takeLock = this.takeLock;  //拿锁
        takeLock.lockInterruptibly();  //拿锁加锁,以保证在调用take()方法的时候只有一个线程
        try {
            while (count.get() == 0) { //如果队列为空
                notEmpty.await(); //则将当前线程阻塞并挂起
            }
            x = dequeue(); //否则,删除头节点
            c = count.getAndDecrement();  //元素个数-1
            if (c > 1)  //判断队列中是否还有元素
                notEmpty.signal(); //如果有,在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费 
        } finally {
            takeLock.unlock(); //释放拿锁,以保证其他线程可以调用take()方法
        }
        if (c == capacity) //表示如果队列中还可以再插入数据 
            signalNotFull(); //在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据 
        return x; //返回删除的那个元素
    }

 remove(Object o)方法

    public boolean remove(Object o) {
        if (o == null) return false; //如果要删除的元素为null,返回false
        fullyLock(); //remove操作要移动的位置不固定,2个锁都需要加锁
        try {
            for (Node<E> trail = head, p = trail.next;
                 p != null;
                 trail = p, p = p.next) {
                if (o.equals(p.item)) {//判断在队列中是否能找到要删除的对象
                    unlink(p, trail);//修改节点的链接信息,同时调用notFull的signal方法 ,唤醒等待的线程
                    return true;
                }
            }
            return false;//如果没有找到,返回false
        } finally {
            fullyUnlock();//2个锁解锁 
        }
    }

remove()方法中的加锁方法(fullyLock):

 void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }

remove()方法中的解锁方法()

 void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }

删除头节点的方法(dequeue,以上方法使用过)

    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值