Java多线程(五)——线程池任务队列LinkedBlockingQueue

这篇文章继续来介绍线程任务队列——LinkedBlockingQueue,LinkedBlockingQueue是基于链接节点的阻塞队列,它是线程安全的。
LinkedBlockQueue是在jdk1.5之后出现的,先看看它的所继承或实现的超类的关系图。
在这里插入图片描述
再看看LinkedBlockingQueue从超类里面所继承过来的方法,最主要的是看AbstractQueue和BlockingQueue
在这里插入图片描述
直接看看BlockingQueue接口里面的方法。

public interface BlockingQueue<E> extends Queue<E> {
    //将指定的元素插入到此队列中,成功就返回true,失败久返回flase
    boolean add(E e);

    //将指定的元素插入到此队列中,如果可以立即执行此操作,而不会违反容量					 //限制, true在成功时 false如果当前没有可用空间,则返回false。
    boolean offer(E e);

   //将指定元素插入到此队列中,如果不能立即执行,线程就一直等待,直至该元
   //该元素被插入到队列中去,或者在等待过程过中线程被中断
    void put(E e) throws InterruptedException;

    //在指定多少时间之后向队列插入指定元素,成功返回ture,失败返回flase
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

  //删除队头元素,如果队列为空,就等待
    E take() throws InterruptedException;

 //等待指定的时间后删除队头元素,如果队列为空就等待
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

   //剩余容量
    int remainingCapacity();
   
   //删除指定的元素 
    boolean remove(Object o);

   //队列中是否包含某元素 
    public boolean contains(Object o);

    //删除队列中所有可用的元素,并将它们添加到给定集合里面
    int drainTo(Collection<? super E> c);

  //删除给定数量的可用元素,并将它们添加到指定的队列中去
    int drainTo(Collection<? super E> c, int maxElements);
}

LinkedBlockingQueue基本实现了BlockingQueue里面的方法,等会看看LinkedBlockingQueue的源码是怎么实现的。

我们知道LinkedBlockingQueue是线程安全的,怎么保证的它的线程安全呢?主要是靠ReentrantLock和Condition来实现的。

 /** Lock held by take, poll, etc */
 //当从队列中取出元素时,所加的锁。
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    //notEmpty等待对象,队列中没元素可取的时候,就把当前取出线程挂起
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    //向队列添加元素时,所加的锁
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    //notFull等待对象,当队列已满时,挂起向队列添加元素的线程
    private final Condition notFull = putLock.newCondition();

下面看看LinkedBlockingQueue中的几个重要的方法:

**

入队方法

**

1.put(E e)

 public void put(E e) throws InterruptedException {
 		//检查插入的是否为空元素,如果为空就跑异常
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        //获取当前的插入锁
        final ReentrantLock putLock = this.putLock;
        //当前队列元素数量
        final AtomicInteger count = this.count;
        //加锁,这是可中断锁
        putLock.lockInterruptibly();
        try {
     		//队列元素已满,等待(阻塞队列)
            while (count.get() == capacity) {
                notFull.await();
            }
            //将node入队
            enqueue(node);
            //统计元素数量+1
            c = count.getAndIncrement();
            //如果队列未满,唤醒其他在等待的队列
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
        //释放锁
            putLock.unlock();
        }
        //如果队列中有元素,唤醒消费者来消费
        if (c == 0)
            signalNotEmpty();
    }

2.offer(E e)

public boolean offer(E e) {
		//非空判断
        if (e == null) throw new NullPointerException();
        //当前队列元素数量
        final AtomicInteger count = this.count;
        //判断队列是否已满
        if (count.get() == capacity)
            return false;
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        //加锁
        putLock.lock();
        try {
        	//如果队列未满
            if (count.get() < capacity) {
            	//将node入队
                enqueue(node);
                c = count.getAndIncrement();
                //如果队列未满,唤醒其他线程向队列添加元素的操作
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        //如果队列中有元素,唤醒其他线程来消费
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

3.offer(E e,long timeout,TimeUnit unit)
这个方法的实现跟offer(E e)差不多,只是这个方法在指定的延迟时间后插入。

public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
            //等待时间超时,直接返回false
                if (nanos <= 0)
                    return false;
                    //利用了Condition的awaitNanos方法来进行阻塞等待				 //			直至超时
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;
    }

上面方法小结:
put(E e)跟 offer(E e)最大的区别是:当用put方法将元素入队时,如果队列元素已满了,put方法会将当前线程阻塞,而offer方法直接返回false。

**

出队方法

**

1.take()
take()方法跟put()的逻辑其实是差不多一样的。

public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //上锁,可中断锁
        takeLock.lockInterruptibly();
        try {
        	//当队列没有元素的时候,就阻塞
            while (count.get() == 0) {
                notEmpty.await();
            }
            //出队
            x = dequeue();
            c = count.getAndDecrement();
            //队列中非空,唤醒其他等待经常出队操作的线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
		//唤醒其他线程来生产元素(入队)
        if (c == capacity)
            signalNotFull();
        return x;
    }

2.poll()

public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
        //队列中有元素
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
                //唤醒其他线程来消费
                if (c > 1)
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        //唤醒生产线程
        if (c == capacity)
            signalNotFull();
        return x;
    }

3.poll(long timeout, TimeUnit unit)

具体跟offer(E e ,long timeout,TimeUnit unit)差不多

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout);
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
            //如果超时 直接返回null
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

出队方法总结:
take进行出队的时候,如果队列中没有元素就会将线程阻塞,而poll方法不会,如果队列没有元素就直接返回null。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JAVA线程基本学习, JAVA线程的特性= 线程池: 本质上是一个对象池, 用来管理线程资源. 在任务执行前, 需要从线程池中拿出线程来执行. 在任务执行完成之后, 需要把线程放回线程池. 线程池好处: 降低资源的消耗, 线程本身是一种资源, 创建和销毁都会消耗CPU内存, 频繁的创建和销毁会浪内存. 提高任务执行的响应速度, 任务执行时, 可以不必等线程创建完成之后在执行,可以直接获取线程执行任务. 提高线程的可管理性, 线程不能无限制的创建, 需要进行统一的分配, 调优和监控. 不适用线程池坏处: 频繁的线程创建和销毁会占用更多的CPU和内积. 频繁的创建和销毁会对gc产生比较大的压力. 线程太多,线程切换带来的开销将不可忽视. 线程太少, 多核CPU得不到充分利用, 浪费资源. 线程池实现原理: 分为三部分: 核心线程池 线程池 队列 拒绝策略 主要流程: 1.判断核心线程池是否已满, 如果不是, 则创建线程执行任务 2.如果核心线程池满了, 判断队列是否满了, 如果队列没满, 将任务放在队列中 3.如果队列满了, 则判断线程池是否已满, 如果没满, 创建线程执行任务 4.如果线程池满了, 按照拒绝策略对任务进行处理 JDK中提供了一个线程池工厂: Executors ,很多工厂方法, 可以创建多种线程池 1.单一线程池 ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); 该线程池只有一个线程, 若多个任务被提交到此线程池, 那么会被缓冲到队列,当线程空闲时,按照FIFO的方式进行处理. 2.固定数量线程池 ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5); 和创建单一线程池类似, 不同的是线程池中有多个线程, 可以并行处理任务, 若多个线任务被提交到此线程池, 会有以下执行过程: 如果线程的数量未达到指定数量, 则创建新线程执行任务. 如果线程池的数量达到了指定数量, 而且此时有线程是空闲的, 则取出空闲线程来执行任务. 如果没有线程是空闲的, 则将任务缓冲到队列, 当线程空闲的时候, 按照FIFO的方式进行处理. FIFO: 先进先出 3.带缓冲的线程池线程池中, 核心线程池长度为0, 线程池最大长度为Integer.MAX_VALUE.以SynchronousQueue作为等待队列, 从而每次往队列中插入一个元素, 必须等待另一个线程从这个队列删除一个元素. 定时调度 4.定时调度的线程池 ExecutorService newCachedThreadPool = Executors.newScheduledThreadPool(); ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5); ExecutorService newWorkStealingPool = Executors.newWorkStealingPool() 线程池家族 线程池的顶层接口是Executor, 这个接口定义了一个核心方法executor(Runnable command), 这个方法最后被ThreadPoolExecutor类实现, 这个方法用来传入任务, 并且该类是线程池的核心类, 构造方法如下 : public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueu, ThreadFactory threadFactory,RejectedExecutionHandler handler); 参数意义: CorePoolSize: 核心线程池大小, 如果核心线程池有空闲的位置, 新的任务就会被核心线程池新建一个线程执行, 执行完毕不会销毁线程, 线程会进入缓冲队列等待再次被运行 MaximunPoolSize: 线程池能创建最大的线程数量, 如果核心线程池和缓冲队列都已经满了, 新的任务就会进来创建新的线程来执行, 但是数量不能超过maximunPoolSize, 否则采取拒绝接受任务策略 KeepAliveTime: 非核心线程能够空闲的最长时间, 超过时间, 线程终止, 这个参数默认只有在线程数量超过核心线程池大小时, 才会起作用. Unit: 时间单位, 和keepAliveTime配合使用. WorkQueue: 缓冲队列, 用来存放等待被执行的任务 ThreadFactory: 线程工厂, 用来创建线程, 一般有三个选择 ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue Handler :拒绝处理策略, 线程数量大于最大线程数量就会拒绝处理策略, 四种策略为 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 Executor接口有一个子接口ExecutorService, ExecutorService的实现类AbstracExecutorService, 而ThreadPoolExecutor正是AbstracExecutorService的子类. ThreadPoolExecutor还有两个常用的方法shutdown和submit,两者都用来关闭线程池, 但是submit有一个结果返回. 线程池任务执行 当执行executor(Runnable command)方法后, 传入一个任务, public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); } } (传入的对象实现了Runnable接口, 这就解释了: 为什么实现多线程的方式中继承Thread类不能使用线程池了) 执行过程: 首先判断任务是否为空, 为空抛出空指针异常, 否则执行下一个判断: 当前线程数量是否小于核心线程池线程数量, 是,则执行addIfUbderCorePollSize(command)方法, 在核心线程池中创建新的线程, 并且执行这个任务 总结: 如果当前线程池中的数目小于corePollSize, 则每来一个任务, 就会创建一个新的线程去执行这个任务 如果当前线程池中的线程数目>=corePollSize, 则每来一个任务, 会尝试将其添加到缓冲队列中, 如果添加成功, 则该任务会等待空闲线程将其取出去执行, 如果添加失败(一般是以为任务队列已经满了), 则会尝试创建新的线程去执行这个任务. 如果当前线程池中的线程数目达到maximunPoolSize, 则会采取任务拒绝策略进行处理. 如果线程池中的线程数量大于corePoolSize时, 如果某线程空闲时间超过keepAliveTime, 线程将被终止, 直至线程池中的线程数目小于等于corePoolSize; 如果允许核心池中的线程设置存活时间, 那么核心池中的线程空闲时间超过keepAliveTime, 线程也会被终止.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值