阻塞队列+线程池应用(Java)1.0

数据结构之队列

队列(queue)是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

线程阻塞的队列

在多线程中,阻塞的意思是,在某些情况下会挂起线程,一旦条件成熟,被阻塞的线程就会被自动唤醒。也就是说,之前线程的wait和notify我们程序员需要自己控制,但有了这个阻塞队列之后我们程序员就不用担心了,阻塞队列会自动管理。
在这里插入图片描述
总结:我们把做put操作的线程当成生产者,把take操作的线程当成消费者。空队列时消费者阻塞,队列满时生产者阻塞。

队列是一种数据结构,数据结构我认为是三元的,包含它的元素、元素之间的关系、元素的操作算法。队列的底层还是数组实现,但是对元素的操作算法不同于数组,所以就变成一个新的数据结构。

分析他的核心方法(put、take),不同的put和take方法会又不同的返回响应,阻塞只是其中一种。下面方法规定了对数组操作的方法。
在这里插入图片描述
检查的意思是队列是否为空,队首元素是什么。

方法类型主要指具体的操作,我们按插入行来讲。
抛出异常:是指队列满时,不阻塞而是抛出异常处理
特殊值:是值添加元素时,返回值,提示是否插入成功。
阻塞:就是线程阻塞,直到添加进队列。
超时:阻塞的优化,设置一个超时时间,防止一值等待,不释放资源。
下面是ArrayBlockingQueue的源码,做参考

//抛出异常
public boolean add(E var1) {
    if (this.offer(var1)) {
        return true;
    } else {
    	//这里抛出异常
        throw new IllegalStateException("Queue full");
    }
}
//返回boolean值
public boolean offer(E var1) {
    checkNotNull(var1);
    ReentrantLock var2 = this.lock;
    var2.lock();

    boolean var3;
    try {
        if (this.count == this.items.length) {
            var3 = false;
            return var3;
        }

        this.enqueue(var1);
        var3 = true;
    } finally {
        var2.unlock();
    }

    return var3;
}
//阻塞
public void put(E var1) throws InterruptedException {
    checkNotNull(var1);
    ReentrantLock var2 = this.lock;
    var2.lockInterruptibly();

    try {
        while(this.count == this.items.length) {
        	//这里阻塞
            this.notFull.await();
        }

        this.enqueue(var1);
    } finally {
        var2.unlock();
    }

}
//超时
public boolean offer(E var1, long var2, TimeUnit var4) throws InterruptedException {
    checkNotNull(var1);
    long var5 = var4.toNanos(var2);
    ReentrantLock var7 = this.lock;
    var7.lockInterruptibly();

    try {
    	//返回值var8
        boolean var8;
        while(this.count == this.items.length) {
        	//控制超时
            if (var5 <= 0L) {
                var8 = false;
                return var8;
            }

            var5 = this.notFull.awaitNanos(var5);
        }

        this.enqueue(var1);
        var8 = true;
        return var8;
    } finally {
        var7.unlock();
    }
}
阻塞队列的实现

我们可以再JUC包中看到各种阻塞队列,下面是常用的三个队列以及它们之间的关系
在这里插入图片描述
Queue是与List平级的存在,它也有对应的实现。阻塞队列也只是队列的一种。
ArrayBlockingQueue:在创建时,必须指定队列长度,是一个有界队列。
LinkedBlockingQueue:创建不需要指定长度,默认为2147483647长度,为int的最大值。
SynchronousQueue:长度0的队列,put时要有take操作。

阻塞队列的线程池应用

什么池的意义:就是为了控制数量,复用元素。
线程池就是为了控制线程数量,复用线程。结合现在的多核CPU,减少线程的切换,可以提高CPU的利用率,提高性能。
在这里插入图片描述
JUC包的线程池结构,它的具体落地实现是ThreadPoolExecutor。我们使用Executors工具类创建的线程池,我们看底层其实都是ThreadPoolExecutor对象,只是参数不同而已。

public class Test {

    public static void main(String[] args) {
        //固定线程数
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        //一个线程数
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        //初始为0线程数,可创建扩容,最大2147483647
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    }
}
    public static ExecutorService newFixedThreadPool(int var0) {
    	//固定线程数
        return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    }
    
    public static ExecutorService newSingleThreadExecutor() {
    	//一个线程数
        return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
    }
    
    public static ExecutorService newCachedThreadPool() {
    	//初始为0线程数,可创建扩容,最大2147483647
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }

日常开发过程中上述的创建线程池的方式建议都不要使用,比如newSingleThreadExecutor()方法创建的线程池,它的阻塞队列长度为2147483647,这太大了。newSingleThreadExecutor()也是一样的。newCachedThreadPool()创建的阻塞队列是SynchronousQueue,可以用,但是它的线程创建最大数为2147483647,不可能创建这么多线程。

我应该自己创建ThreadPoolExecutor线程池对象,参数自己设置,接下来我们分析一下ThreadPoolExecutor有哪些参数可以配置。
下面是ThreadPoolExecutor的全参创建的构造方法,可以看出它一共可以设置七个参数。忘了也没事,看源码回忆。

public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
        this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
        this.mainLock = new ReentrantLock();
        this.workers = new HashSet();
        this.termination = this.mainLock.newCondition();
        if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
            if (var6 != null && var7 != null && var8 != null) {
                this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
                this.corePoolSize = var1;
                this.maximumPoolSize = var2;
                this.workQueue = var6;
                this.keepAliveTime = var5.toNanos(var3);
                this.threadFactory = var7;
                this.handler = var8;
            } else {
                throw new NullPointerException();
            }
        } else {
            throw new IllegalArgumentException();
        }
    }

在这里插入图片描述
在这里插入图片描述

  1. corePoolSize:线程核心数:线程最小生存数。
  2. maximumPoolSize:最大线程数,可以创建的最大线程数
  3. keepAliveTime:生存时间,线程在大于核心数时,线程的生存时间
  4. unit:时间单位
  5. workQueue:阻塞队列,可以缓存新的任务
  6. threadFactory:线程管理工厂,负责创建线程
  7. handler:拒绝策略,当线程数到达最大,且阻塞队列空间也已满,对新的任务的拒绝策略
    (如果需要JDK1.8 中文API文档,可以私信我)

七大参数对应的结构图
在这里插入图片描述
新任务进来执行流程图
在这里插入图片描述

拒绝策略是当线程数最大,阻塞队列也满了,无法处理新任务时的动作。有哪些动作呢
在这里插入图片描述

  1. AbortPolicy:抛出异常,阻止系统正常运行
  2. CallerRunsPolicy:不接收也不抛异常,回退给生产者线程
  3. DiscardOldestPolicy:抛出队列中等待最长的任务,尝试将新任务添加到队列中
  4. DiscardPolicy:不接收不抛异常,直接丢弃

线程核心数于最大线程数怎么设置呢
分析我们当前的业务需求,是CPU密集型(算法复杂,会一直在执行计算),IO密集型(网络通信,会有大量阻塞)。

  • CPU密集型:CPU核数+1线程数。可以减少线程切换(例子:8核则9线程)
  • IO密集型:CPU核数/(1-阻塞系数)。阻塞系数一般在0.8-0.9之间。(例子:8核、阻塞系数0.9则80线程)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值