BlockingQueue
- 框架知识梳理
概述
- 本质是队列,满足队列的原则(FIFO)
- 所有的阻塞式队列都是有界的 - 当队列定义好之后,大小就不可变
- 阻塞:当队列已满的时候,再试图放入的线程会被阻塞;当队列为空的时候,再试图拿去的线程会被阻塞
- 要求队列中的元素必须非空
- 方法
抛出异常
返回值
阻塞
定时阻塞
添加
add
offer - true/false
put
offer
获取
remove
poll - null
take
po
实现类
1.ArrayBlockingQueue - 阻塞式顺序队列
- 底层是基于数组进行存储
- 使用的时候需要指定容量,定义好之后容量不可变
package cn.tedu.concurrent;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个阻塞式队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
// 存放元素
// queue.add("a");
// queue.add("b");
// queue.add("c");
// queue.add("d");
// queue.add("e");
// 获取元素
// 队列为空
// 抛出异常
// System.out.println(queue.remove());
// 返回null
// System.out.println(queue.poll());
// 产生阻塞
// System.out.println(queue.take());
// 定时阻塞
System.out.println(queue.poll(5, TimeUnit.SECONDS));
// 队列已满
// 抛出异常
// queue.add("f");
// 返回false
// boolean b = queue.offer("f");
// System.out.println(b);
// 产生阻塞
// queue.put("f");
// 定时阻塞
// queue.offer("f", 5, TimeUnit.SECONDS);
// System.out.println(queue);
}
}
2.LinkedBlockingQueue - 阻塞式链式队列
- 底层是基于节点实现的
- 在使用的时候可以不指定容量。如果指定了容量,指定多大就是多大;如果不指定容量,默认容量是Integer.MAX_VALUE -> 231-1。因为在实际生产环境中,一般不会向一个队列中添加21亿个值,所以一般会认为这个队列如果不指定容量就是无界的
3.PriorityBlockingQueue - 具有优先级的阻塞式队列
- 在使用的时候可以指定容量也可以不指定。如果不指定容量,默认容量是11
- 在拿取元素的时候,会对元素进行排序(自然排序 - 自然排序如果不指定一般是升序的) - 要求元素所对应的类必须实现Comparable接口,重写compareTo方法指定比较规则
- 如果使用迭代遍历的方式,则此时排序规则无效
package cn.tedu.concurrent;
import java.util.concurrent.PriorityBlockingQueue;
//具有优先级的阻塞式队列
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
/* PriorityBlockingQueue<String> queue = new PriorityBlockingQueue<>(5);
queue.put("a");
queue.put("h");
queue.put("d");
queue.put("w");
queue.put("x");
queue.put("z")*/
;
/*
1.在使用的时候可以指定容量也可以不指定。如果不指定容量,默认容量是11
2. 在拿取元素的时候,会对元素进行排序(自然排序 - 自然排序如果不指定一般是升序的) - 要求元素所对应的类必须实现Comparable接口,重写compareTo方法指定比较规则
*/
/* for(int i=0;i<6;i++){
System.out.println(queue.take());
}*/
PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>();
queue.put(new Student("wyf", 40, 70));
queue.put(new Student("wyh", 25, 80));
queue.put(new Student("wyb", 30, 10));
queue.put(new Student("why", 60, 59));
queue.put(new Student("wyl", 50, 60));
queue.put(new Student("wyx", 11, 11));
for (int i = 0; i < 7; i++) {
System.out.println(queue.take());
}
/* for (Student s : queue) {
System.out.println(s);
}*/
}
}
class Student{
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
public int compareTo(Student o) {
return o.score - this.score;
}
}
4. SynchronousQueue - 同步队列
- 使用的时候不需要指定容量,默认容量为1
5. BlockingDeque - 阻塞式双向队列
- 允许两端添加或者获取元素
- 继承了BlockingQueue
ConcurrentMap - 并发映射
概述
- 本质上是一个Map,存储的元素依然是键值对结构
- 保证映射的并发能力以及线程安全的能力
特点
- 底层是基于数组+链表结构来存储数据
- 默认初始容量是16,默认加载因子是0.75,扩容的时候,默认每次增加一倍
- 才用了分段(桶)锁机制。在后续的版本中,ConcurrentHashMap为了提高效率,在分段锁的基础上,引入了读写锁机制: a:读锁,允许多个线程读,不允许线程写;b:写锁:只允许一个线程写,不允许线程读
- 在JDK1.8中,引入了CAS(Compare And Swap,比较和交换)无锁算法保证线程安全性
- 从JDK1.8开始,如果一个桶中的元素个数超过了8个的时候,这个桶中的链表会扭转成一棵红黑树;如果不足7个,那么红黑树扭转回链表
- 红黑树
ConcurrentNavigableMap - 并发导航映射
- 提供了用于截取子映射的方法
- 一般使用的是实现类ConcurrentSkipListMap - 并发跳跃表映射
- 跳跃
要求元素必须有序 针对原来的数据进行提取形成跳跃表,跳跃表可以向上一层层提取,但是最顶层的跳跃表中的元素至少有2个,一般是2-4个 跳跃表是典型的以空间换时间的产物 适用于查询多而增删少 如果新添节点,这个节点是否要提取到上一层跳跃表中,遵循"抛硬币"原则 跳跃7表的查询时间复杂度是O(logn) package cn.tedu.concurrent; import java.util.concurrent.ConcurrentSkipListMap; public class ConcurrentMapDemo { //并发哈希映射 public static void main(String[] args) { ConcurrentSkipListMap<String, Integer> map = new ConcurrentSkipListMap<>(); map.put("a", 3); map.put("d", 3); map.put("e", 3); map.put("w", 3); map.put("q", 3); map.put("m", 3); map.put("e", 3); //可以截取map表 System.out.println(map); System.out.println(map.subMap("a", "w")); System.out.println(map.headMap("e")); System.out.println(map.tailMap("q")); } }
ExecutorService - 执行器服务
概述
- 线程池的意义:减少服务器端的线程的创建和销毁,做到线程的重用
- 线程池在创建的时候需要定义一定数量的线程
- 每接受一个请求都会创建一个线程(核心线程)去处理这个请求
- 核心线程在处理完请求之后不会被销毁而是等待下一个请求过来
- 在核心线程达到数量之前,每次过来的请求都会去创建一个新的核心线程
- 如果所有的核心线程都被占用,那么后续的请求会被放入工作队列中。工作队列本质上是一个阻塞式队列
- 如果工作队列也被全部占用,则线程池会创建临时线程去处理这个请求。如果临时线程执行完任务之后,存活指定的一段时间,如果这段时间内没有新的任务处理,则这个临时线程才会被kill掉
- 如果临时线程也被全部占用,则新来的请求会交给RejectedExecutionHandler - 拒绝执行处理器处理
- 工作队列中的请求只会交给核心线程
package cn.tedu.concurrent;
import java.util.concurrent.*;
//执行器服务
public class ExcutorServiceDemo {
public static void main(String[] args) {
//创建线程池
//corePoolSize 核心线程数量
//maximumPoolSize 线程池的大小=核心线程数+临时线程数
//keepAliveTime -临时线程的存活时间
//unit -时间单位
//workQueue - 工作队列
//handle -拒绝执行处理器
ExecutorService es = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(5), //工作队列
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒绝执行" + r);
}
});
//提交线程去执行
for (int i = 0; i < 18; i++) {
es.execute(new EsRunable());
}
es.shutdown();
}
}
class EsRunable implements Runnable{
@Override
public void run() {
System.out.println("hello wyf");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Callable
- 是JDK1.5提供的一个用于并发和返回结果的线程
- Callable和Runnable的区别:
- 返回值:Runnable无返回值,Callable可以定义返回值
- 启动方式:Runnable可以通过Thread类或者是线程池来启动,但是Callable只能通过线程池启动
- 异常机制:Runnable不允许抛出异常,Callable允许抛出异常,那么这就意味着Callable如果报错可以利用全局方式处理
package cn.tedu.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorServiceDemo2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/*
特点:
1. 没有核心线程全部都是临时线程
2. 临时线程的数量是Integer.MAX_VALUE
人为的认为这个线程池中包含的线程数量是无限的
3. 临时线程用完之后允许存活1min
4. 工作队列是一个同步队列
5. 大池子小队列
适用场景:高并发、短任务场景
*/
// ExecutorService es = Executors.CachedThreadPool();
/*
特点:
1. 没有临时线程全部都是核心线程
2. 工作队列是一个阻塞式链式队列,默认容量是Integer.MAX_VALUE
可以认为能够存储无限多的任务
小池子大队列
适用场景:长任务
*/
ExecutorService es = Executors.newFixedThreadPool(5);
// 将结果封装成Future对象,泛型表示的结果的实际类型
Future<String> f = es.submit(new CThread());
es.shutdown();
System.out.println(f.get());
}
}
// 泛型表示返回值的类型
class CThread implements Callable<String> {
@Override
public String call() throws Exception {
return "SUCCESS";
}
}
ScheduledExecutorService - 定时执行器服务
- 可以实现定时效果
- 是很多定时器的底层机制
ForkJoinPool - 分叉合并池
- 分叉:将一个大任务拆分成多个小任务然后分布在不同的核上执行; 合并:将分叉出去的线程的执行结果进行汇总
- 分叉合并能够有效的提高CPU的利用率
- 数据量越大,分叉合并相对循环而言的效率就越高
- 分叉合并在分配的时候,会根据核上已有的线程数量来进行尽量均衡的分配
- 为了防止慢任务导致整体效率降低,分叉合并采取了work-stealing(工作窃取)策略 - 当一个核上的任务执行完成之后,这个核不会空闲而是去随机扫描一个核然后从这个核的任务列表的尾端来“偷”一个任务回来执行
package cn.tedu.concurrent;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
//分叉合并池
public class ForkJoinDemo {
/* public static void main(String[] args) {
long begin = System.currentTimeMillis();
long sum = 0;
for (long i = 1; i <= 100000000000L; i++) {
sum += i;
}
//计算结果:932356074711512064
System.out.println(sum);
}*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
long begin = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> f = pool.submit(new Sum(1, 00000000000L));
pool.shutdown();
System.out.println(f.get());
long end = System.currentTimeMillis();
System.out.println(end-begin);
}
}
class Sum extends RecursiveTask<Long> {
private static final long serialVersionUID = -5007766220903293812L;
private long start;
private long end;
public Sum(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= 10000) {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long mid = (start + end) / 2;
Sum left = new Sum(start, mid);
Sum right = new Sum(mid + 1, end);
// 分叉
left.fork();
right.fork();
// 合并
return left.join() + right.join();
}
}
}