高并发编程(阻塞队列,put,take源码, 线程池)

谁无风暴劲雨时,守得云开见月明


阻塞队列BlockingQueue

队列:先进先出数组。
既队列满了等待队列为空的时候进行写入,如果队列空了则等待队有数据后进行消费。
接口结构:
在这里插入图片描述
集合派生出List(可变数组),Set(不可重复队列),Queue(先进先出队列),先进先出队列后派生除了BlockingQueue(阻塞队列),Deque(链表双端队列),abstractQueue(非阻塞队列),阻塞队列又派生出ArrayBlockingQueue(数组队列),LinkedBlockingQueue(链表数组)

阻塞队列的应用场景:BlockingQueue的使用场景:多线程并发处理、线程池。

常用API:

 public static void test1(){
        //队列的大小是3
        ArrayBlockingQueue blockingqueue = new ArrayBlockingQueue<>(3);System.out.println(blockingqueue.add('a'));
        System.out.println(blockingqueue.add('b'));
        System.out.println(blockingqueue.add('c'));
        System.out.println("========================");
        System.out.println(blockingqueue.element());    //查看队首元素
        System.out.println(blockingqueue.remove());
        System.out.println(blockingqueue.remove());
        System.out.println(blockingqueue.remove());
        //add  添加   remove   移除
        // 抛出异常 Exception in thread "main" java.lang.IllegalStateException: Queue full
        // System.out.println(blockingqueue.add('a'));
        // 抛出异常 Exception in thread "main" java.util.NoSuchElementException
        // System.out.println(blockingqueue.remove());//offer  添加    poll  移除
		 // 不抛出异常 false
		System.out.println(blockingqueue.offer('d')); 
		// 不抛出异常 null
		System.out.println(blockingqueue.poll()); 
		
		//	阻塞等待
		blockingqueue.put("d"); // 队列没有位置 一直阻塞
		System.out.println(blockingqueue.take());  // 没有这个元素,一直阻塞

		//  有限阻塞等待
		blockingqueue.offer("d",2,TimeUnit.SECONDS);   // 等待  添加 超过2秒就退出
		blockingqueue.poll(2,TimeUnit.SECONDS); // 等待  移除 超过2秒就退出
    }

LinkedBlockingQueue和ArrayBlockingQueue数组的区别只是底层的存储的方式不一样,其余区别都是一样的。

BlockingQueue实现原理

在阻塞线程之中最重要的是2个线程之间的相互通讯,即当容器满了后需要等待数据取出,如果线程空了则要等待线程的写入。

源码:
ArrayBlockingQueue属性:

	//数组存储
/** The queued items */
	final Object[] items;
	//take取出的数组下标
	/** items index for next take, poll, peek or remove */
	int takeIndex;
	//put添加的数组下标
	/** items index for next put, offer, or add */
	int putIndex;
	//总数
	/** Number of elements in the queue */
	int count;
	
	/*
	 * Concurrency control uses the classic two-condition algorithm
	 * found in any textbook.
	 */
	//定义一个重入锁,担任线程挂起等待责任
	/** Main lock guarding all access */
	final ReentrantLock lock;
	
	//为空条件
	/** Condition for waiting takes */
	private final Condition notEmpty;
	//未满条件
	/** Condition for waiting puts */
	private final Condition notFull;

从属性上我们就已经能看出一些端倪,其实ArrayBlockingQueue是数组来存储,当容量满的时候线程挂起进行等待,到为空的时候,也是线程挂起等待写入。

Put方法:

public void put(E e) throws InterruptedException {
	    checkNotNull(e);
	    final ReentrantLock lock = this.lock;
	    lock.lockInterruptibly();
	    try {
			//如果当前队列已满,将线程移入到notFull等待队列中,
	        while (count == items.length)
	        	//notFull.await();即代表到期线程已经满了。进入挂起状态
	            notFull.await();
			//满足插入数据的要求,直接进行入队操作
	        enqueue(e);
	    } finally {
	        lock.unlock();
	    }
	}
	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)
	        putIndex = 0;
	    count++;
		//通知消费者线程,当前队列中有数据可供消费
	    notEmpty.signal();
	}

take源码:

public E take() throws InterruptedException {
	    final ReentrantLock lock = this.lock;
	    lock.lockInterruptibly();
	    try {
			//如果队列为空,没有数据,将消费者线程移入等待队列中
	        while (count == 0)
	            notEmpty.await();
			//获取数据
	        return dequeue();
	    } finally {
	        lock.unlock();
	    }
	}

同步队列SynchronousQueue

没有容量,即放进去一个必须拿去一个处理后才能继续放入

常用API:
put放入,take取出

public static void test1() throws InterruptedException {
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();   // 同步队列new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" put 1");
            try {
                synchronousQueue.put("1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" put 2");
            try {
                synchronousQueue.put("2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" put 3");
            try {
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }

线程池

线程池的本质是减少内存IO的开销,控制的线程生命周期,对线程进行管理。

☆☆☆☆☆☆☆☆:线程池的创建一定不要使用Executor创建,而使用ThreaPoolExcutor,这样可以让管理者更加明确的对线程池进行管理,规避资源创建风险。

FixedThreadPool、SingleThreadPool、CachedThreadPool、ScheduledThreadPool创建线程的时候默认最大值都是默认的Integer.Max_Value≈21亿(2^31-1),从而导致OOM(内存溢出)

在这里插入图片描述

线程池的创建

自动创建
不建议使用的方式

public class SaleTicketDemo1 {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程的线程池
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);// 固定线程数量的线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();// 可伸缩的线程池
        try {
            for (int i = 1; i <100 ; i++) {
                //  使用了线程池之后,使用线程池来创建
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //  线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

手动创建

public class SaleTicketDemo1 {
    public static void main(String[] args) {
    	//第一个参数,coresize核心数量,一般基于CPU的数量相等
    	//第二个参数,maximumPoolSize 最大线程池大小
    	//第三个参数,keepAliveTime 超时时间,没人调用就会释放s
    	//第四个参数,TimeUnit 超时单位
    	//第五个参数,BlockingQueue<Runnable>   传入入的阻塞队列模型
    	//第六个参数,ThreadFactory 线程工厂
    	//第七个参数,RejectedExecutionHandler 拒绝策略
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2, 5,3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());try {
            //  最大承载 = queue + max
            //  超出最大承载,抛出RejectedExecutionException
            for (int i = 1; i <= 9 ; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

4个拒绝策略:
AbortPolicy:超过最大承载数了还有线程进来,不处理并报错
CallerRunsPolicy:哪来的去哪里,是main方法执行
DiscardPolicy:超过最大承载数了就会丢掉任务,不会抛出异常
DiscardOlderstPolicy:队列满了,尝试去和最早的竞争也不会抛出异常

最大线程到底该如何定义?

q.u密集型:几核就是几,可以保证CPU效率最高!

IO密集型:判断你系统中十分消耗IO的线程数量,设置数值大于这个数就行了,一般可以设置2倍!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
阻塞队列线程池的搭配使用是为了提程序的性能和效率,通过避免不必要的线程创建和销毁,减少资源消耗。阻塞队列线程池中起到任务缓存和通信的作用,它允许生产者(添加任务的线程)和消费者(执行任务的工作线程)之间进行有效的协作。当阻塞队列满时,添加任务的线程阻塞,直到队列中有空闲位置;当队列为空时,工作线程阻塞,直到队列中有新任务加入。 以下是阻塞队列线程池搭配使用的一些优点: 1. **提响应速度**:线程池可以重用现有线程,减少线程创建和销毁的开销,从而提性能和响应速度。 2. **避免资源过度消耗**:线程的创建和销毁是一个相对重量级的操作,线程池通过重用线程来执行多个任务,避免了资源的过度消耗。 3. **提线程的可管理性**:线程池可以对线程进行统一的分配、调优和监控,提了线程的可管理性。 4. **避免线程竞争死锁**:阻塞队列提供了等待/通知功能,用于线程间的通信,从而避免了线程竞争死锁的情况发生。 在使用阻塞队列线程池时,需要注意以下几点: 1. 选择合适的阻塞队列类型,如ArrayBlockingQueue、LinkedBlockingQueue等,根据不同的应用场景选择不同的队列特性。 2. 合理配置线程池的参数,如核心线程数、最大线程数、队列容量等,以确保线程池能够效运行。 3. 考虑线程池的拒绝策略,当阻塞队列满时,需要有合适的策略来处理新添加的任务,如抛出异常、丢弃任务或创建新线程等。 相关问题: 1. 什么是阻塞队列? 2. 什么是线程池? 3. 阻塞队列线程池中的作用是什么?

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值