谁无风暴劲雨时,守得云开见月明
阻塞队列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倍!