Java集合|容器大全
2021-02-02 17:17:31 78 举报
AI智能生成
Java集合|容器大全
作者其他创作
大纲/内容
图
Collection
Map
普通容器
Collection
List
ArrayList
结构构成
int modCount
记录更改次数,应用于fast-fail
int size
当前数组元素个数
int DEFAULT_CAPACITY = 10
Object[] elementData
Object[] EMPTY_ELEMENTDATA = {}
Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
方法
构造
ArrayList()
数据数组初始化指向内部默认的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
ArrayList(int initialCapacity)
长度>0,数据数组初始化对应长度
长度=0,数据数组初始化指向内部默认的空数组EMPTY_ELEMENTDATA<br>
长度<0,异常
ArrayList(Collection<? extends E> c)
增
add(E e)
add(int index, E element)
从指定位置元素后挪一位(复制实现),然后设值
add(E e, Object[] elementData, int s)
如果发现超过数组长度,则先调用grow()扩容<br>
grow()
grow(int minCapacity)<br>
初始化时调用的默认无参构造函数
数组长度直接变为DEFAULT_CAPACITY
否则
数组长度变为1.5倍
不能超过Integer.MAX_VALUE - 8
删
remove(int index)
remove(Object o)
若o为null,遍历,移除第一个null
若o不为null,遍历,移除第一个equals的
fastRemove(Object[] es, int i)
若在末尾,直接置为null
否则复制移动
改
set(int index, E element)
查
contains(Object o)
indexOfRange(Object o, int start, int end)
若o为null,遍历,找到第一个null
若o不为null,遍历,找到第一个equals的
其他
iterator()
hasNext()<br>
next()
检查modCount,不一致则fast-fail抛出ConcurrentModificationException<br>
获取数组后再次检查下标,越界了同样抛出ConcurrentModificationException<br>
remove()
检查modCount,不一致则fast-fail抛出ConcurrentModificationException<br>
若越界、删除失败说明有并发操作,抛出ConcurrentModificationException
forEachRemaining(Consumer<? super E> action)
sort(Comparator<? super E> c)
LinkedList<br>
结构构成
int size = 0
Node<E> first
Node<E> last
方法
构造
LinkedList()
LinkedList(Collection<? extends E> c)
增
linkLast(E e)
add(E e)<br>
offer(E e)
删
unlinkFirst(Node<E> f)
remove(Object o)
改
set(int index, E element)
从近的一端开始遍历
查
get(int index)
从近的一端开始遍历
indexOf(Object o)
从近的一端开始遍历
Vector<br>
结构构成
Object[] elementData
int elementCount
int capacityIncrement<br>
扩容时的默认大小
方法
构造
Vector()
默认大小10
Vector(int initialCapacity)
Vector(int initialCapacity, int capacityIncrement)
默认capacityIncrement=0
增
grow(int minCapacity)
默认扩容到2倍
如果capacityIncrement更大,则扩容到oldCapacity+capacityIncrement
不推荐原因
性能差
子类
Stack
方法
push(E item)
pop()
peek()
不推荐原因
继承了Vector
可以修改栈中元素,破坏了封装。应该为组合关系
Set
Hashset<br>
结构构成
HashMap<E,Object> map
Object PRESENT = new Object()
作为所有key的val
方法
构造
HashSet()
HashSet(int initialCapacity, float loadFactor)
HashSet(int initialCapacity, float loadFactor, boolean dummy)
包可见
为子类构造方法服务,构造出LinkedHashMap
增
add(E e)
删
remove(Object o)
查
contains(Object o)
子类
LinkedHashset<br>
方法
构造
LinkedHashSet()
LinkedHashSet(int initialCapacity, float loadFactor)
Treeset<br>
结构构成
NavigableMap<E,Object> m
Object PRESENT = new Object()
作为所有key的val
方法
构造
TreeSet()
TreeSet(NavigableMap<E,Object> m)
Queue
Deque
ArrayDeque<br>
结构构成
Object[] elements
int head
初始0
int tail
初始0
int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
方法
构造
ArrayDeque()
默认大小16/17
ArrayDeque(int numElements)
旧版大小为2的n次幂
ArrayDeque(Collection<? extends E> c)
增
int inc(int i, int modulus)
int dec(int i, int modulus)
addFirst(E e)
放入--head的地方
从大端开始向左延伸
addLast(E e)
放入tail++的地方
从小端开始向右延伸
offer
同add
grow(int needed)
增长长度为Math.max(needed,0.5倍)
如果长度<64则扩大到两倍<br>
如果之前head有元素,还需要后挪
删
pollFirst()
空队列返回null
pollLast()
空队列返回null
removeFirst()
空队列抛出异常
removeLast()
空队列抛出异常
removeFirstOccurrence(Object o)
delete(int i)
包可见性
删除数组中间某一位数
查
peekFirst()
空队列返回null
peekLast()
空队列返回null
getFirst()
空队列抛出异常
getLast()
空队列抛出异常
LinkedList
PriorityQueue<br>
结构构成
int DEFAULT_INITIAL_CAPACITY = 11
Object[] queue
Comparator<? super E> comparator
int modCount
int size
当前队列元素个数
方法
构造
PriorityQueue()
默认大小DEFAULT_INITIAL_CAPACITY
默认无比较器
PriorityQueue(int initialCapacity,Comparator<? super E> comparator)
增
offer(E e)
先检查扩容
再插入
siftUp(int k, E x)
若初始化时传入了Comparator则使用<br>
否则调用compareTo方法<br>
grow(int minCapacity)
长度<64?<br>
扩大到2倍
否则
扩大到1.5倍
删
poll()
空队列返回null
重新建堆
将队列最后一个元素值赋值给队首
从指定的位置开始,逐层向下与当前点的较小的孩子交换,直到比两个孩子都小
查
indexOf(Object o)
Map
分类
HashMap<br>
结构构成
常量
int DEFAULT_INITIAL_CAPACITY = 16
默认初始容量
int MAXIMUM_CAPACITY = 1 << 30
最大容量
float DEFAULT_LOAD_FACTOR = 0.75f
默认负载因子
int TREEIFY_THRESHOLD = 8
链表长度的树化阈值
MIN_TREEIFY_CAPACITY = 64
树化容量阈值
UNTREEIFY_THRESHOLD = 6
逆树化阈值
变量
Node<K,V>[] table
jdk7
空
jkd8
延迟初始化到第一次put
Set<Map.Entry<K,V>> entrySet
int size
int modCount
int threshold
初始时的默认容量
开始扩容的阈值
float loadFactor
内部类Node<K,V><br>
int hash
K key
V value
Node<K,V> next
指示哈希冲突时同一个位置的下一个节点
内部类TreeNode<K,V>
TreeNode<K,V> parent
TreeNode<K,V> left
TreeNode<K,V> right
TreeNode<K,V> prev
为链表化服务
boolean red
方法
构造<br>
HashMap()
全属性默认值
HashMap(int initialCapacity)
threshold值设为2的n次幂<br>
服务于hash掩码
HashMap(int initialCapacity, float loadFactor)
threshold值设为2的n次幂<br>
服务于hash掩码
增
JDK1.7
put(K key, V value)
addEntry(int hash, K key, V value, int bucketIndex)
createEntry(int hash, K key, V value, int bucketIndex)
头插法
如果下次get会很快取到
JDK1.8
put(K key, V value)
putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
若未初始化则先初始化<br>
若hash位置上为null,说明之前没放过这个key也没有哈希冲突,直接新建节点放上去<br>
否则
如果hash相同,且key相同或者满足equals<br>
直接替换节点value<br>
返回旧value
如果是树节点
如果是链表节点
遍历如果找到相同key
否则在链表结尾新建节点
如果链表长度达到TREEIFY_THRESHOLD=8<br>
如果数组长度没超过MIN_TREEIFY_CAPACITY,说明主要原因是长度不够的问题,选择resize()
否则树化当前位置
如果链表长度>threshold,调用resize<br>
删
remove(Object key)
removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)<br>
从哈希位置上找到对应的节点<br>
树节点<br>
单节点或者是链表的头节点
直接替换成next节点<br>
链表节点
更换next节点,挖出
Node#removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable)
改
treeifyBin(Node<K,V>[] tab, int hash)
首先形成一个树节点的列表,完成next指针
然后以根节点开始树化
TreeNode#treeify(Node<K,V>[] tab)
从头结点开始遍历处理
如果是第一个节点,则标记为根节点,直接标为黑色
否则依次插入树中并调整
根据hash值比较,小于走左子树,大于走右子树,直到当前节点为null,插入到当前位置
从这个新节点x开始平衡新树,调整颜色、进行必要的旋转,返回计算出的新的root
x先定为red
如果x无父节点,说明x是根直接返回x,调整结束
如果x父节点是black,直接返回原root,调整结束<br>
否则x父节点为red
如果x的叔节点也为red<br>
x的爷节点变为red,x的父节点和叔节点都变为black
x变为x的爷节点,递归处理
否则
旋转
重复变色
根据上一步计算出的新root,调整根节点
直接把新root从链表中间挖出来,插入到链表头即可,处理相关next和prev指针
查
get(Object key)
其他
hash(Object key)
JDK7
4次位运算+5次异或
JDK8
右移16位扰动
1次位运算+1次异或
resize()<br>
JDK7
依次复制
复制的时候会翻转链表
JDK8
未初始化
指定了初始化容量
数组大小指定
新threshold值变为newCap * loadFactor<br>
未指定
数组大小取默认值16
新threshold值变为newCap * loadFactor<br>
12
已初始化
数组扩容2倍
新threshold值变为2倍(数组大小比默认值大之后)
遍历复制
单点
计算新的hash值直接复制
树点
链表点
JDK1.7
JDK1.8
保持原有链表顺序
利用两个链表分别储存同样位置的节点、位置增加一倍的节点
只需要看hash值的n+1位是0还是1
遍历形成两个链表,复制到新数组相应位置上
问题
重写equals和hashCode方法
并发问题
1.7
resize时并发造成循环链表,get时发生死循环
1.8
数据丢失<br>
当多线程put的时候,当index相同而又同时达到链表的末尾时,<br>另一个线程put的数据会把之前线程put的数据覆盖掉
子类
LinkedHashMap<br>
结构构成
Entry<K,V> head
Entry<K,V> tail
boolean accessOrder
true
access-order
get一个元素后,这个元素被加到最后
false
insertion-order
内部类Entry<K,V><br>
extends HashMap.Node<K,V>
Entry<K,V> before, after
方法
构造
LinkedHashMap()
默认为插入顺序
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
增
同父类
newNode(int hash, K key, V value, Node<K,V> e)
newTreeNode(int hash, K key, V value, Node<K,V> next)
linkNodeLast(LinkedHashMap.Entry<K,V> p)
删
同父类
afterNodeRemoval(Node<K,V> e)
查
同父类
WeakHashMap
结构构成
ReferenceQueue<Object> queue = new ReferenceQueue<>()
内部类Entry<K,V>
extends WeakReference<Object>
构造
方法
同1.7HashMap
问题
key为Integer时,-128-127的Integer会被一直缓存,不会被释放
TreeMap<br>
结构构成
Comparator<? super K> comparator
Entry<K,V> root
int size = 0
内部类Entry
K key
V value
Entry<K,V> left
Entry<K,V> right
Entry<K,V> parent
boolean color = BLACK
方法
构造
TreeMap()
TreeMap(Comparator<? super K> comparator)
增
put(K key, V value)
检查key是否为null
计算得到插入的位置,若已有key则替换val,否则新建节点插入
平衡红黑树
删
remove(Object key)
deleteEntry(Entry<K,V> p)
查
get(Object key)
getEntry(Object key)
HashTable<br>
结构构成
同HashMap
方法
构造
默认大小11,每次扩容到2n+1
同步的JDK1.7HashMap
问题
hash索引计算方法为取余,因为数组长度没有定位2的次幂
父类Dictionary被废弃
子类
Properties<br>
结构构成
volatile Properties defaults<br>
volatile ConcurrentHashMap<Object, Object> map
同步容器
Collections.synchronizedXxx()<br>
List
SynchronizedList
结构构成<br>
Collection<E> c
List<E> list
Object mutex
锁对象
方法
构造
SynchronizedList(List<E> list)
SynchronizedList(List<E> list, Object mutex)
全部方法需要同步
SynchronizedRandomAccessList
Map
SynchronizedMap<br>
结构构成
final Map<K,V> m
Object mutex
方法
构造
SynchronizedMap(Map<K,V> m)
SynchronizedMap(Map<K,V> m, Object mutex)
全部方法需要同步
Set
SynchronizedSet<br>
问题
通过Iterator、Spliterator或Stream遍历时,需要手动在外部做好同步
并发容器
List
CopyOnWriteArrayList<br>
原理
写时复制快照,之后用新数组替换原数组
结构构成
Object lock = new Object()
JDK8?之后改ReentrantLock为synchronized(lock)<br>
volatile Object[] array
accessed only via getArray/setArray
方法
构造
CopyOnWriteArrayList()
增
add(E e)
进入同步
复制一个新数组,长度为旧数组长度+1
设置数组末尾的新值
替换成新数组
删
remove(int index)<br>
复制剩余值到新数组中
改
set(int index, E element)
如果新值与旧值不同,克隆出新数组,在新数组上修改再替换
如果相同,也要再写一遍保证volatile语义
查
get(int index)<br>
elementAt(Object[] a, int index)
其他
iterator
保存着原数组的引用,因此迭代过程中原数组发生的改变不影响此次迭代
弱一致性
Map
ConcurrentHashMap<br>
原理
1.7
Segment+ HashEntry
基于currentLevel(默认16)划分出了多个Segment来对key-value进行存储
put、remove会加锁,get和containsKey不会加锁
get到的值为null时会加锁
#size
在不加锁的情况下遍历所有的段,读取其count以及modCount,<br>这两个属性都是volatile类型的,并进行统计,再遍历一次所有的段,比较modCount是否有改变。<br>如有改变,则再尝试两次。
如执行了三次上述动作,仍然有问题,则遍历所有段,分别进行加锁,然后进行计算。<br>计算完毕后释放所有锁,从而完成计算动作。
1.8
CAS+synchronized<br>
每个bin的第一个Node插入、删除、替换使用CAS
#size
累加各个bin中Node的个数计算得到,而且这一过程不加锁,即得到的size值不一定是最新的
结构构成
常量
同HashMap
int MIN_TRANSFER_STRIDE = 16
int RESIZE_STAMP_BITS = 16
int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1
int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS
变量
volatile Node<K,V>[] table
延迟初始化到第一次put
volatile Node<K,V>[] nextTable
volatile long baseCount
volatile int sizeCtl
指示表初始化和扩容,默认值为0
当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75
当为负的时候,说明表正在初始化或扩张
-1表示初始化
-N 表示有N-1个线程正在进行扩容操作
如果table未初始化,表示table需要初始化的大小
如果table初始化完成,表示table的扩容阈值,默认是table大小的0.75倍
volatile int transferIndex
volatile int cellsBusy
volatile CounterCell[] counterCells
Unsafe U = Unsafe.getUnsafe()
内部类<br>
Node<K,V>
int hash
K key
volatile V val
volatile Node<K,V> next
TreeNode<K,V>
同HashMap
TreeBin<K,V>
TreeNode<K,V> root
volatile TreeNode<K,V> first
volatile Thread waiter
volatile int lockState
树化后当作整棵树储存在数组中
ForwardingNode<K,V>
Node<K,V>[] nextTable
在转移的时候放在头部的节点,是一个空节点<br>
ReservationNode<K,V>
方法
构造
ConcurrentHashMap()
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
初始化sizeCtl
增
put(K key, V value)
putVal(K key, V value, boolean onlyIfAbsent)
数组为null或长度为0,先初始化
如果哈希位置上没有节点,新建节点CAS插入即可。如果插入失败进入下一步<br>
否则有节点
否则
同步这个节点
如果取出来的节点的hash值是MOVED(-1)的话,则表示当前正在对这个数组进行扩容并复制到新的数组,则当前线程也去帮助复制,之后再继续插入的工作
如果是链表(hash大于0),进行更新或者插入,并统计链表长度
如果是TreeBin,说明是树,调用TreeBin#putTreeVal添加到树中
如果链表长度大于阈值了,调用#treeifyBin,树化或扩容
链表长度+1
addCount(long x, int check)<br>
删
remove(Object key)
改
casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v)
cas原子操作,在指定位置设定值
setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)
原子操作,在指定位置设定值
treeifyBin(Node<K,V>[] tab, int index)
如果数组长度<64,则选择扩容2倍
否则
同步头结点
先遍历链表,处理TreeNode的next指针<br>
新建TreeBin,构造了红黑树
调用#setTabAt,将此处节点换成TreeBin<br>
hash为-2
tryPresize(int size)<br>
查
tabAt(Node<K,V>[] tab, int i)
Unsafe
返回节点数组的指定位置的节点的原子操作
get(Object key)<br>
其他
initTable()
循环判断当前数组是否为null或长度为0,当否时就返回,否则进入循环
如果sizeCtl<0,说明有线程正在初始化,yield当前线程<br>
否则尝试将sizeCtl CAS为 -1,表示当前线程要开始初始化,失败了就回到上一步<br>
否则CAS成功
新建数组,若初始化时指定了长度(保存在sizeCtl)则使用,否则默认长度16
sizeCtl长度变为数组长度的3/4
Queue
ConcurrentLinkedQueue
结构
volatile Node<E> head,tail = new Node<E>()
默认头、尾节点都是指向 item 为 null 的哨兵节点 。 新元素会被插入队列末尾,出队时从队列头部获取一个元素
tail结点不总是最后一个结点
减少volatile变量的写开销
内部类Node
volatile E item
volatile Node<E> next
使用 UnSafe(现在改为VarHandle) 的 CAS 算法来保证出入队时操作链表的原子性。
方法
增
#offer(E e)
新建Node
从tail节点开始一直向后找到last Node
CAS插入
若失败重新循环
CAS更新tail
失败也没关系
如果发现遍历到的节点next指向自身
说明该节点已被删除,不在队列上
只能从head重新开始遍历
删
<b>#poll</b><br>
查
其他
#size
并发环境下不一定准确
Set
CopyOnWriteArraySet
AbstractQueuedSynchronizer<br>
依赖工具类
LockSupport
作用
调用Unsafe类方法挂起、唤醒线程
结构构成
Unsafe unsafe = Unsafe.getUnsafe()
方法
#park()
=Unsafe#park(false, 0L);
如果调用 park 方法的线程已经拿到了与 LockSupport 关联的许可证,则马上返回
否则调用线程被持续阻塞挂起
#park(Object blocker)
当钱程在没有持有许可证的情况下调用 park 方法而被阻塞挂起时 ,这个 blocker 对象会被记录到该线程内部。
使用诊断工具可以观察线程被阻塞 的原因,所以 JDK 推荐我们使用带有 blocker 参数的 park 方法,<br>并且blocker被设置为 this ,这样当在打印线程堆栈排查问题时就能知道是哪个类被阻塞了
诊断工具是通过调用 getBlocker(Thread) 方法来获取 blocker 对象的,
线程被激活后清除blocker
#parkNanos(long nanos ,Object blocker)
#unpark(Thread thread )
调用 park方法而被阻塞的线程会返回
当一个线程调用 unpark 时,如果参数 thread 线程没有持有 thread 与 LockSupport 类关联的许可证, 则 让 thread 线程持有。
所以一个线程不会park两次
如果 thread 之前因调用 park ()而被挂起,则调用unpark 后,该线程会被唤醒。
结构构成
Thread exclusiveOwnerThread
当前拥有此排他锁的线程
volatile int state
状态信息,实现类定义具体意义
ReentrantLock
当前线程获取锁的可重入次数
ReentrantReadWriteLock
高16位表示读状态,也就是获取该读锁的次数
低 16 位表示获取到写锁的线程的可重入次数
semaphore
当前可用信号的个数
CountDownlatch
表示计数器当前的值
FutureTask
当前任务的状态
内部类Node
static Node SHARED = new Node()
当做参数,标记该Node的线程是获取共享资源时被阻塞挂起后放入 AQS 队列的
static Node EXCLUSIVE = null
当做参数,标记该Node的线程是获取独占资源时被挂起后放入AQS 队列的
volatile int waitStatus
记录当前线程等待状态
CANCELLED (线程被取消了)<br>
1
线程超时或响应了中断<br>
SIGNAL ( 线程需要被唤醒)
-1
每次都由SIGNAL节点唤醒下一个节点,充当闹钟
CONDITION (线程在条件队列里面等待〉
-2
PROPAGATE (释放共享资源时需要通知其他节点〕
-3
volatile Node prev
volatile Node next
volatile Thread thread
对应进入AQS队列里面的一个线程
Node nextWaiter
队列中下一个节点的类型
volatile Node head
固定是一个dummy node,因为它的thread成员固定为null<br>
volatile Node tail
尾节点,请求锁失败的线程,会包装成node,放到队尾
内部类 ConditionObject
Node firstWaiter
条件队列的头
Node lastWaiter
条件队列的尾
可以直接访问 AQS 对象内部的变量 ,比如 state 状态值和 AQS 队列
ConditionObject 是条件变量,每个条件变量对应一个条件队列 (单向链表队列),其用来存放调用条件变量的await方法后被阻塞的线程
方法
基础
#isHeldExclusively
子类重写用来判断当前锁是独占还是共享
#enq(Node node)
给定节点,入队<br>
当一个线程获取锁失败后该线程会被转换为Node节点,然后将该节点插入到AQS的阻塞队列。
若未初始化则先初始化,head和tail节点均指向一个空节点
CAS操作先替换tail节点,然后更改next指针
返回旧tail
#addWaiter(Node mode)
给定节点类型,添加新的等待线程节点,入队
返回新tail
操作资源
独占资源
#acquire(int arg)
当前线程调用tryAcquire 方法尝试获取资源
具体为设置状态变量 state 的值,成功则直接返回
失败则将当前线程封装为类型为 Node. EXCLUSIVE 的 Node 节点后插入到 AQS 阻 塞 队列的尾部
调用LockSupport#park( this )方法挂起自己
如果循环过程中发生了中断,要重新设置好中断标志
#acquireQueued(final Node node, int arg)
用一个死循环不断去执行tryAcquire,直到获取锁
每次循环都会判断是否可以尝试获取锁(p == head),如果可以,那么尝试(tryAcquire(arg))。<br><span style="font-size: inherit;"> 如果尝试获取锁成功,那么函数的使命就达到了,执行完相应收尾工作,然后返回。</span><br>如果 不可以尝试 或者 尝试获取锁却失败了,那么阻塞当前线程(parkAndCheckInterrupt)。<br>如果当前线程被唤醒了,又会重新走这个流程。被唤醒时,是从parkAndCheckInterrupt处唤醒,然后从这里继续往下执行。<br>
把node的有效前驱(node类型不是CANCELLED的)找到,并且将有效前驱的状态设置为SIGNAL,之后便返回true代表马上可以阻塞了<br>
因为存在去除CANCELLED节点的操作,至少要循环两次才能阻塞自己。第一次设置前驱节点状态,第二次阻塞自己
返回interrupted标志<br>
#acquirelnterruptibly(int arg)<br>
获取资源或挂起时会响应中断,将对应Node状态标记为CANCELLED,之后移除该节点
#tryAcquireNanos(int arg, long nanosTimeout)
LockSupport.parkNanos(this, nanosTimeout);
#release(int arg)
当前线程尝试调用 tryRelease 方法释放资源<br>
head状态不为0即唤醒head后继
共享资源
#acquireShared(int arg)
当前线程尝试调用tryAcquireShared方法尝试获取资源<br>
设置状态变量 state 的值,成功则直接返回
失败则调用doAcquireShared将当前线程封装为类型为 Node.SHARED 的 Node 节点后插入到 AQS 阻 塞 队列的尾部
调用LockSupport#park( this )方法挂起自己
#releaseShared(int arg)
当前线程尝试调用 tryReleaseShared 方法释放资源<br>
调用 LockSupport.unpark(thread )方法激活 AQS 队列里面被阻塞的一个线程(thread)
被激活的线程则使用 tryAcquireShared 尝试,看当前状态变量 state的值是否能满足自己的需要,满足则该线程被激活,然后继续 向下运行
否则还是会被放入 AQS 队列并被挂起
条件变量
Condition#await
当线程调用条件变量的await()方法时,在内部会构造一个类型为Node.CONDITION的node节点,<br>然后将该节点插入条件队列末尾,之后当前线程会释放获取的锁(也就是会操作锁对应的state变量的值),<br>并被阻塞挂起。<br>
这时候如果有其他线程调用lock.lock()尝试获取锁,就会有一个线程获取<br>到锁,如果获取到锁的线程调用了条件变量的await ()方法,则该线程也会被放入条件<br>变量的阻塞队列,然后释放获取到的锁,在await()方法处阻塞。
Condition#signal
当另外一个线程调用条件变量的signal方法时,在内部会把条件队列里面队头的一个线程节点<br>从条件队列里面移除并放入AQS的阻塞队列里面,然后调用unpark激活线程。 <br>
实现类
Lock
ReentrantLock<br>
结构构成
state
当前线程获取锁的可重入次数
Sync sync
内部类<br>
Sync extends AbstractQueuedSynchronizer
#nonfairTryAcquire(int acquires)
#tryRelease(int releases)
FairSync extends Sync
#tryAcquire(int acquires)
如果state=0且队列中前驱只有head节点,CAS尝试获取锁<br>
如果重入,state+1<br>
方法
构造
ReentrantLock()
默认非公平锁
ReentrantLock(boolean fair)
锁<br>
#lock()
#lockInterruptibly()
ReentrantReadWriteLock
结构构成
Sync sync
state
高16bit作为读锁的计数范围
低16bit作为写锁的计数范围
ReadLock readerLock = new ReadLock(this)
WriteLock writerLock = new WriteLock(this)
内部类
Sync extends AbstractQueuedSynchronizer<br>
NonfairSync extends Sync
FairSync extends Sync
ReadLock implements Lock
WriteLock implements Lock
HoldCounter
记录各个线程分别拿走了多少读锁
int count = 0
long tid = getThreadId(Thread.currentThread())
防止垃圾不被回收
ThreadLocalHoldCounter extends ThreadLocal<HoldCounter>
方法
Queue
ArrayBlockingQueue <br>
结构
items 数组:存放队列元素
int putindex :入队元素下标
int takelndex :出队下标
int count :队列元素个数
ReentrantLock lock
Condition notEmpty
Condition notFull
方法
同下,不过只用了一个全局锁,管理进和出
LinkedBlockingQueue<br>
结构
单向无界链表
Node<E> head,Node<E>last<br>
默认都是指向 item 为 null 的哨兵节点
AtomicInteger count
记录队列元素个数
ReentrantLock putLock
控制同时只能有一个线程可以获取锁,在队列尾部添加元素
Condition notFull = putLock.newCondition();
当队列满时,执行put的线程会被放入这个条件队列进行等待<br>
ReentrantLock takeLock<br>
控制同时只有一个线程可以从队列头获取元素
Condition notEmpty = takeLock.newCondition();
当队列为空时,执行take的线程会被放入这个条件队列进行等待<br>
方法
构造
LinkedBlockingQueue()
默认大小Integer.MAX_VALUE<br>
LinkedBlockingQueue(int capacity)
增
#put(E e)
加锁入队
如果队列满阻塞当前线程,加入notfull等待唤醒<br>
阻塞时可被中断抛出异常
一定条件下唤醒notempty、notfull
#offer(E e)
如果队列满直接返回false
#add(E e)
如果队列满抛出异常
取
#take
从队列头部获取并移除一个元素
如果队列为空则阻塞当前线程直到队列不为空<br>如果在阻塞时被其他线程设置了中断标志,则被阻塞线程会抛出异常返回 <br>
#poll
从队列头部获取并移除一个元素
空队列直接返回null
#peek
从队列头部获取一个元素
空队列直接返回null
#remove(Object o)
删除队列里面指定的元素,有则删除并返回true,没有则返回false。 <br>
加双重锁
#size
PriorityBlockingQueue
结构
queue 数组:存放队列元素
默认长度11
平衡二叉树实现优先排序
int size :队列元素个数<br>
ReentrantLock lock
Condition notEmpty
队列空时,出队线程将阻塞在这里
无notnull,因为是无界队列,因此put操作非阻塞
volatile int allocationSpinLock
标识当前是否正在扩容
相当于AQS的state,持有这个state才可以准备新数组以扩容
方法
同上
#tryGrow(Object[] array, int oldCap)
如果oldCap < 64, 新容量等于2(oldCap + 1)
如果oldCap >=64, 新容量等于1.5oldCap
#size
加锁获取
DelayQueue
结构
PriorityQueue<E> q:存放队列元素
元素需要实现Delayed接口
只有元素到期后才能将其出队
到期时间短的Delayed元素排在队列前面
ReentrantLock lock
Condition available = lock.newCondition();
Thread leader<br>
Leader- Follower 模式的变体,减少不必要的线程等待
它总是等待获取队首
方法
存
#offer(E e)
如果添加的元素是最先过期的<br>
leader = null;<br>
available.signal();
取
#poll
获取并移除队头过期元素
如果没有过期元素则返回 null
#take
#size
other
CountDownLatch
方法
#await<br>
主线程调用后阻塞直到计数器为0
#countDown
子线程任务调用,计数减一
CyclicBarrier
方法
#await<br>
某一线程调用后,计数器减一
若此时计数器不为0,调用线程阻塞直到计数器为0
否则当前线程执行构造函数里传入的任务,之后唤醒其他阻塞线程
可重用
Semaphore
方法
#release<br>
某一线程调用后,计数器加一
#acquire(int n)
某一线程调用后,阻塞直到信号量为n
Executor
线程池
ThreadPoolExecutor
构成
AtomicInteger ctl<br>
高3位用来表示线程池状态
低29位用来表示线程个数
初始值ctlOf(RUNNING, 0)<br>
线程池状态
RUNNING
接受新任务并且处理阻塞队列里的任务
SHUTDOWN
拒绝新任务但是处理阻塞队列里的任务
STOP<br>
拒绝新任务并且抛弃阻塞队列里的任务 ,同时会中断正在处理的任务
TIDYING<br>
所有任务都执行完(包含阻塞 队列里面的任务)后当前线程池活动线程<br>数为 0 , 将要调用 terminated 方法
TERMINATED<br>
终止状态 。 terminated 方法调用完成 以后的状态
CAPACITY
线程最 大个数(低29位) 00011111111111111111111111111111
runStateOf(int c) { return c &~CAPACITY ; )
获取高3位(运行状态)
workerCountOf(int c) { return c & CAPACITY ; )
获取低29位(线程个数)
ctlOf (int rs , int we) { return rs I we; )
计算ctl 新值(线程状态与线程个数 )
ReentrantLock mainLock
控制新增 Worker线程操作的原子性
Condition termination = mainLock.newCondition();
线程调用 awaitTermination 时用来存放阻塞的线程
方法
ThreadPoolExecutor (<br> int corePoolSize,<br> int maximumPoolSize,<br> long keepAliveTime,<br> TimeUnit unit,<br> BlockingQueue <Runnable> workQueue ,<br> ThreadFactory threadFactory,<br> RejectedExecutionHandler handler<br>) <br>
corePoolSize :线程池核心线程个数
没有任务执行时线程池的大小
只有阻塞队列满时才会创建超出这个数量的线程<br>
maximunPoolSize : 线程池最大线程数量。
keeyAliveTime :存活时间
如果当前线程池中的线程数量比核心线程数量多 ,并且<br>是闲置状态, 则这些闲置的线程能存活的最大时间
workQueue :用于保存等待执行的任务的阻塞队列
基于数组的有界ArrayBlockingQueue
基于链表的无界 LinkedBlockingQueue
最多只有一个元素的同步队列 SynchronousQueue<br>
优先级队列 PriorityBlockingQueue
RejectedExecutionHandler :饱和策略
当队列满并且线程个数达到 maximunPoolSize后采取的策略
AbortPolicy (抛出异常〉、
CallerRunsPolicy (使用调用者所在线程来运行任务)
DiscardOldestPolicy (调用 poll 丢弃一个任务,执行当前任务)
DiscardPolicy (默默丢弃,不抛出异常〉
#execute(Runnable command)
如果当前线程池中线程个数小 于 corePoolSiz e , 会向 workers 里面新增一个核心线程( core 线程)执行该任务.
否则如果当前线程池处于 RUNNING 状态则添加当前任务到任务队列 。
如果添加任务成功 ,则对线程池状态进行二次校验
如果当前线程池状态不是 RUNNING 了则把任务从任务队列移除,移除后执行拒绝策略
如果二次校验通过,则重新判断当前线程池里面是否还有线程,如果没有则新增一个线程
如果添加任务失败 ,则说明任务队列己满,尝试新开启线程来执行该任务
如果当前线程池中线程个数>maximumPoolSize 则执行拒绝策略。
#addWorker(Runnable firstTask, boolean core)
通过 CAS 操作增加线程数
把并发安全的任务添加到 workers 里面,并且启动线程执行任务
#shutdown
调用 shutdown 方法后 ,线程池就不会再接受新的任务了,但是工作队列里面 的任务还是要执行的 。
该方法会立刻返回,并不等待 队列任务完成再返回
#shutdownNow
调用 shutdownNow 方法后,线程池就不会再接受新的任务了,并且会丢弃工作队列里面的任务 , 正在执行的任务会被中断 ,
该方法会立刻返回 ,并不等待激活的任务执行完成。返回值为这时候队列里面被丢弃的任务列表。
ScheduledThreadPoolExecutor
Executors
newCachedThreadPool(ThreadFactory threadFactory)
核心线程个数0,和最大线程个数INTEGER.MAX
阻塞队列为同步队列,最大长度为1<br>
SynchronousQueue
keeyAliveTime = 60s
newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
核心线程个数和最大线程个数都为 nThreads
阻塞队列最大长度为INTEGER.MAX
LinkedBlockingQueue
keeyAliveTime = 0
newSingleThreadExecutor(ThreadFactory threadFactory)
核心线程个数和最大线程个数都为 1
阻塞队列最大长度为INTEGER.MAX
LinkedBlockingQueue
keeyAliveTime = 0
0 条评论
下一页