原子性操作–Atomic 原子性
- 原子性操作实际上就是针对属性来提供线程安全的方法,在底层会自动采用CAS来保证线程安全。
- volatile是Java提供的轻量级的线程同步机制
阻塞队列 BlockingQueue
原则:
- 遵循FIFO
- 往往是有界的,容量固定不变
- 具有阻塞特性:如果队列已满,则试图放入的线程会被阻塞;如果队列为空,就会阻塞尝试获取的线程
- 不允许元素null
方法:
抛出异常 返回特殊值 产生阻塞 定时阻塞 添加 add - IllegalStateException offer - false put offer 获取 remove - NoSuchElementException poll - null take poll
ConcurrentMap -并发映射
- ConcurrentHashMap底层基于数组h+链表来存储数据,数组的每一个位置称之为桶
- 默认初始容量16(桶的数量),加载因子0.75
- 如果指定容量,底层会进行计算,实际容量一定是2的n次方
- 每次扩容每次增加一倍的桶数,扩容之后需要rehash操作
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
//底层算法
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
- 分段锁,封桶锁机制,不是把整个映射锁起来
- 在jdk1.7引入读写锁
ConcurrentNavigableMap - 并发导航映射
- ConcurrentNavigableMap提供了用于截取子映射的方法
- ConcurrentNavigableMap本身是一个接口,所以更多的是使用它的实现类ConcurrentSkipListMap - 并发跳跃表映射
跳跃表
- 针对有序集合
- 适合于增删少,查询多的场景
- 跳跃表可以进行多层提取,最上层的跳跃表的元素个数不能少于2个
- 跳跃表是典型的“以空间换时间“的产物
- 在跳跃表中新添元素提取到上层跳跃表,遵循抛硬币原则
- 跳跃表的时间复杂度是O(logn)
ExecutorService
概述
- 本质上是一个线程池
线程池:减少服务器的线程的创建和销毁,做到连接的复用 - 刚开始创建时空的,里面不包含任何线程
- 每接受一个请求,线程池都会创建一个线程(核心线程)来处理这个请求,在创建线程的时候就需要指定线程的数量
- 在核心线程达到指定数量之前,来一个请求就会创建一个新的线程来处理这个请求,直到线程满了就不再创建
- 核心线程处理完这个请求不会销毁,等待下一个请求
- 如果核心线程全部沾满,新来的请求会暂存在工作队列,工作队列本身是一个阻塞式队列
- 当核心线程有空的时候,就会从工作队列中取出交给核心线程处理
- 如果工作队列被全部占用,有接受到新的请求,会将这个请求叫个临时线程来处理
- 当临时线程处理完请求之后不会立即结束,而是会存活一段时间,如果在这个时间段内接受到新的请求,会处理新的请求,如果没有接收到请求,就会被kill掉
- 临时线程不会从工作队列中拿出请求处理
- 当临时线程全部占用,那么新来的请求就会交给执行助手来拒绝处理,在实际开发中,如果需要拒绝请求,可能会产生多部操作,例如记录日志,页面跳转,请求重发
- ScheduledExecutorService:定时调度执行器服务。在线程池的基础上加入了定时调度效果。这个线程池本身是很多定时调度机制的底层实现
内存模型
栈内存:执行方法- 计算 栈内存是线程独享的
堆内存:存储对象,线程共享
方法区:存储类信息(字节码,静态,常量)。线程共享的
本地方法栈:执行本地方法,线程独享
本地方法是指在Java中用native声明但使用其他语言实现的方法
pc计数器/寄存器:指令计数,线程独享
如果需要计算一台服务器的线程承载量,考虑线程独享部分。
在jdk1.8中,栈内存最小128k
pc计数器一般只占几个字节,可以忽略
红黑树
定义
a. 红黑树本质上一棵自平衡二叉查找树
b.特征:
- i. 所有节点的颜色非红即黑
- ii. 根节点为黑色
- iii. 红节点的子节点一定黑节点,黑节点的子节点可以是红节点也可以是黑节点
- iv. 最底层的叶子节点一定是黑色的空节点
- v. 从根节点到任意一个叶子节点的路径中经过的黑色节点个数一致,即黑节点高度是一致的
- vi. 新添的节点颜色一定是红的
c. 修正:
- i. 涂色:父子节点为红,叔父节点为红,将父节点核叔父节点涂成黑色,然后将祖父节点涂成红色
- ii. 左旋:父子节点为红,叔父节点为黑,且子节点是右子叶,那么以子节点为轴进行左旋
- iii. 右旋:父子节点为红,叔父节点为黑,且子节点是左子叶,那么以父节点为轴进行右旋
d. 红黑树的时间复杂度是O(logn)
Callable
定义:
- Callable是Java提供的一种定义线程的方式,在使用的时候通过泛型执行返回值类型
Callable和Runnable的区别
| Runnable | Callable | |
|---|---|---|
| 返回值 | 没有返回值 | 通过泛型来指定返回值的类型 |
| 启动方式 | 1. 通过Thread的start方法启动 2. 通过线程池的submit或者是execute方法执行 | 通过线程池的submit方法执行 |
| 容错机制 | 不允许抛出异常,所以不能利用全局方式(例如Spring中的异常通知)处理 | 允许抛出异常,所以可以利用切面或者是全局方式来处理异常 |
分叉合并池
- Fork:分叉。将一个大任务拆分成多个小任务交给多个线程执行
- Join:合并。将拆分出来的小任务的执行结果进行汇总
- 分叉合并的目的是为了提高CPU的利用率
- 在数据量相对小的时候,循环会比分叉合并快;数据量越大,分叉合并的优势越明显
- 分叉合并在进行的时候,导致其他程序的执行效率显著降低,所以分叉合并一般是在周末的凌晨来进行
- 分叉合并在分配子线程的时候,尽量做到每个核上的任务均匀:少的多分,多的少分
- 在分叉合并中,为了减少"慢任务"带来的效率降低,采取"work-stealing"(工作窃取)策略:当一个核将它的任务队列处理完成之后,这个核并不会闲下来,会随机扫描一个核,然后从被扫描核的任务队列尾端"偷取"一个任务回来执行
Lock - 锁
概述
- 相对于synchronized更加灵活和精细
- ReadWriteLock - 读写锁
- a. 在使用的时候,需要用这个接口的实现类ReentrantReadWriteLock
- b. 在加锁的时候,需要先通过对应的方法来获取读锁或者写锁
- 公平和非公平策略
- a. 非公平策略下,线程会直接抢占执行权,在资源有限的前提下,线程之间抢到的次数不一样
- b. 公平策略下,线程不是直接抢占执行权,而是去抢占入队顺序。宪曾之间的执行次数是基本一致的
- c. 默认情况下,使用的是非公平策略
- d. 相对而言,非公平策略的效率会更高一些
其他
- CountDownLatch:闭锁/线程递减锁。对线程进行计数,在计数归零之前会让线程陷入阻塞,直到计数归零才会放开阻塞 - 一波线程结束之后开启另一波线程
- CyclicBarrier:栅栏。对线程进行计数,在计数归零之前让线程陷入阻塞,直到计数归零为止才会放开阻塞 - 线程到达同一个地点之后再分别执行
- Exchanger:交换机。用于交换2个线程之间的信息
- Semaphore:信号量。线程只有获取到信号之后才能执行,执行完成之后需要释放信号。如果所有的信号都被占用,那么后来的线程就会被阻塞
版权声明:本文为Niklaus1028原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。