一、容器分类
- Collection是一个个元素的加到容器;
- Map是一对对元素的加入容器;
- Queue主要是为高并发提高服务;
物理上只有两种分类:连续存储的数组 + 非连续存储的链表
Vector、HashTable是最早期的容器,每个方法都自带Sychronized,基本不用,只需要了解其数据结构;
Map高并发时使用ConcurrentHashMap主要是因为它读取数据的速度快,插入数据的速度未必会比HashTable、SychronizedHashMap快;
(发展历程:HashTable — HashMap — SynchronizedMap — ConcurrentHashMap)
Collection高并发时用Queue,可以保证线程安全同时效率高,要保证元素的一致性,可以使用Set里的同步Set数据结构;
(发展历程:Vector — List/Set — Queue)
二、Queue
与List的区别主要是添加了许多对多线程友好的api(offer、peek、poll、put、take)
-
BlockingQueue:在queue的接口方法上添加了两个方法put和take在队列满了和空了时会阻塞(add/remove方法会报异常,offer/poll不报异常但会退出程序),底层用LockSupport的park和unpark实现了Condition.await()方法导致阻塞(生产者消费者模型);
-
ArrayBlockingQueue:有界数组式阻塞队列;
-
LinkedBlockingQueue:链式阻塞队列;
-
PriorityQueue:小顶堆排序,根据加入的元素的大小从小到大输出,不是按照加入时间的先后输出;
-
DelayQueue:按照加入时设置的定时时间先后输出;
-
SynchronousQueue:一个线程先执行queue.take()并阻塞,当另一个线程执行queue.put()时阻塞线程会拿到该数据并不再阻塞,但是该queue中始终不能存放对象,仅仅作为两个线程传递数据的手段;
-
TransferQueue:多个线程等待其他线程来传输对象,队列可以支持多个线程之间传递数据,而SynchronousQueue只能支持两个线程之间传递数据;
三、线程池
- Runnable:定义线程执行任务的接口;(接口)
- Callable = Runnable + return:在Runnable的基础上增加了一个执行完任务后将来会返回的一个结果值;(接口)
- Future:存放Callable接口中将来返回的结果值;(接口)
- FutureTask = Future + Runnable:可以执行任务并将任务的返回结果存放在对象自身上;(类)
Callable可以执行一个带返回值的任务,但是他的返回值需要存储在Future中,而FutureTask实现了Runnable和Future,所以他可以执行一个带返回值的任务,并将返回值存在自己的对象中,future执行任务的线程和主线程是异步的,但是主线程从future中获取返回值是同步阻塞的。
public class FutureTaskDemo {
public static void main(String[] args) {
FutureTask<String> ft = new FutureTask<>(()->{
return "future task";
});
/** 起一个线程异步执行future任务 */
new Thread(ft).start();
try {
/** 从Future中获取返回值的操作是阻塞的 */
System.out.println(ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程池分类
- ThreadPoolExecutor:普通线程池;(一个任务队列)
- ForkJoinPool:将总任务分解成多片执行然后再汇总;(多个任务队列)
- WorkStealingPool:每一个线程有自己的一个任务队列,当一个线程任务队列空了后,可以从其他线程的队列中拿任务来执行;
七大参数
- 核心线程数
- 最大线程数
- 线程空闲存活时间(大于核心线程数的线程会被OS回收)
- 空闲时间单位
- 任务阻塞队列
- 线程工厂
- 拒绝策略:JDK默认提供了四种,但是这是可以自定义的,所以准确的说没有具体上限(实际中都是自定义的处理策略,一般会把没处理的任务丢到消息队列或者数据库,然后用日志记录还有哪些任务没被处理)
默认的线程池
-
SingleThreadExecutor:只有一个线程,保证顺序执行任务;
为什么不直接new一个线程?——线程池有任务队列,有生命周期管理; -
CachedThreadPool:核心线程数为0,最大线程数为Integer.MAX_VALUE,线程存活时间为0,任务队列为SynchronousQueue也就是说有一个任务来了必须有一个线程来执行,不能放到队列里;
-
FixedThreadPool:固定线程数量,核心线程和最大线程都是一样的一个固定的值;具体使用多少线程数可以结合压力测试用一个经验公式计算;
-
ScheduledThreadPool:用来执行定时任务的线程池,任务队列使用的是DelayedWorkQueue,加入的任务都带有一个延迟执行的时间,时间到了用一个线程去执行;
note:用线程池应对亿级流量:先将流量分发到多个边缘服务器,每个服务器中存有的并发量仍然很高,用线程池加消息队列解决。