java多线程进阶学习1
前言
本章学习知识点
为了解释ThreadLocal类的工作原理,必须同时介绍与其工作甚密的其他几个类
在Thread类存在一行代码如下:
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap类的定义是在ThreadLocal类中,真正的引用却是在Thread类中。
ThreadLocal中set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 设置ThreadLocal时看了初始化了内部类
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
从上面源码可以看到ThreadLocalMap中用于存储数据的entry定义:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
接下来在看下ThreadLocalMap的set方法,从中我们可以发现这个Map的key是ThreadLocal类的实例对象hash值和map的length做与运算,value为用户的值
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocal中get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的。
Tip: 如果把一个共享的对象直接保存到ThreadLocal中,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。 所以要在保存到ThreadLocal之前,通过克隆或者new来创建新的对象,然后再进行保存。
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量(比如本次请求的用户信息),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal的内存泄露问题
从ThreadLocalMap中的Entry方法的源码(WeakReference),我们知道ThreadLocalMap是使用ThreadLocal的弱引用作为Key的。如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄露。
ThreadLocalMap设计时的对上面问题的对策:
ThreadLocalMap的getEntry函数为例
顺便提一句:ThreadLocalMap内部类初始容量16,负载因子2/3,解决冲突的方法是再hash法,也就是:在当前hash的基础上再自增一个常量。
https://blog.csdn.net/CringKong/article/details/80560937
https://www.cnblogs.com/iou123lg/p/9464385.html
https://www.cnblogs.com/doit8791/p/10971420.html
https://www.cnblogs.com/tong-yuan/p/AbstractQueuedSynchronizer.html
CAS操作+volatile关键字+改造的CLH队列
首先AQS内部维护了一个变形的CLH队列,一个基于AQS实现的同步器,这个同步器管理的所有线程,都会被包装成一个结点,进入队列中,所有线程结点,共享AQS中的state(同步状态码)。
AQS中的state
状态码是一个volatile
变量,**而对状态码的修改操作,全部都是CAS操作,这样就保证了多线程间对状态码的同步性,**这种方式也是我们之前所说的CAS常用的操作模式。
核心方法是acquire和release
acquire
while (当前同步器的状态不允许获取操作) {
如果当前线程不在队列中,则将其插入队列
阻塞当前线程
}
release
if (新的状态允许某个被阻塞的线程获取成功)
解除队列中一个或多个线程的阻塞状态
三大关键操作:同步器的状态变更、线程阻塞和释放、插入和移出队列。
三个基本组件:同步器状态的原子性管理 ,线程阻塞与解除阻塞 ,队列的管理
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
整个框架的核心就是如何管理线程阻塞队列,该队列是严格的FIFO队列,因此不支持线程优先级的同步。同步队列的最佳选择是自身没有使用底层锁来构造的非阻塞数据结构,业界主要有两种选择,一种是MCS锁,另一种是CLH锁。其中CLH一般用于自旋,但是相比MCS,CLH更容易实现取消和超时,所以同步队列选择了CLH作为实现的基础。
最好先了解下CLH队列的算法.
private transient volatile Node head;
private transient volatile Node tail;
下面是内部类Node源码
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// voatile属性
volatile int waitStatus;
volatile Node prev; // 上一个节点的指针
volatile Node next; // 下一个节点的指针
volatile Thread thread;
Node nextWaiter;
// 是否是共享
final boolean isShared() {
return nextWaiter == SHARED;
}
// 取下一个节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
变种的CLH队列,CLH队列其实就是采用了状态代理布尔值。根据不同的状态来决定线程的不同行为。
一个时间段内,只能有一个线程可以操作共享资源,这就是独占模式。
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
AQS中有四个核心的顶层入口方法:
acquire(int)、release(int)、acquireShared(int)、releaseShared(int)
以AQS为基础实现的同步器类,只需要合理使用这些方法,就可以实现需要的功能。
显而易见:acquire(int)、release(int)是独占模式中的方法。
而acquireShared(int)、releaseShared(int)是共享模式中的方法。
入队操作:CLH是FIFO队列,每次加入队列的新元素都会放在当前尾节点后面.如果某一个线程获取了同步状态,其他线程就都处于自旋状态,那么新增线程就必须是线程安全的操作,不然无法保证顺序性,因此增加节点采用CAS操作.
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
// 构造线程节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 并发处理 尾节点有可能已经不是之前的节点 所以需要CAS更新
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 第一个入队的节点或者是尾节点后续节点新增失败时进入enq
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
一个死循环,本身没有锁,可以多个线程并发访问
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 第一次循环执行,此时head, tail都为null,用CAS的方式创建一个空的Node作为头结点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 。将当前线程的Node结点(简称CNode)的prev指向tail,然后使用CAS将tail指向CNode
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
节点进入同步队列之后,就进入了一个自旋的过程,每个线程节点都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中并会阻塞节点的线程
// *************** 获取锁 *************************
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前线程节点的前驱节点
final Node p = node.predecessor();
// 前驱节点为头节点且成功获取同步状态
if (p == head && tryAcquire(arg)) {
//设置当前节点为头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//是否阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前驱节点的同步状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
前驱节点为-1 后续节点被阻塞
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞线程
return Thread.interrupted();
}
// *************** 释放锁 *************************
public final boolean release(int arg) {
if (tryRelease(arg)) {//同步状态释放成功
Node h = head;
if (h != null && h.waitStatus != 0)
//直接释放头节点
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒后续节点
LockSupport.unpark(s.thread);
}
总结下独占式同步状态的获取和释放:在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease方法释放同步状态,然后唤醒头节点的后继节点。
独占流程:
获取独占锁流程:
调用入口方法acquire(arg)
调用模版方法tryAcquire(arg)尝试获取锁,若成功则返回,若失败则走下一步
将当前线程构造成一个Node节点,并利用CAS将其加入到同步队列到尾部,然后该节点对应到线程进入自旋状态
自旋时,首先判断其前驱节点是否为头节点&是否成功获取同步状态,两个条件都成立,则将当前线程的节点设置为头节点,如果不是,则利用
LockSupport.park(this)
将当前线程挂起 ,等待被前驱节点唤醒释放独占锁流程:
调用入口方法release(arg)
调用模版方法tryRelease(arg)释放同步状态
获取当前节点的下一个节点
利用
LockSupport.unpark(currentNode.next.thread)
唤醒后继节点(接获取的第四步)
共享式同步状态调用的方法是acquireShared
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 入队操作
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 自旋
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
共享锁流程
获取共享锁流程
调用acquireShared(arg)入口方法
进入tryAcquireShared(arg)模版方法获取同步状态,如果返返回值>=0,则说明同步状态(state)有剩余,获取锁成功直接返回
如果tryAcquireShared(arg)返回值<0,说明获取同步状态失败,向队列尾部添加一个共享类型的Node节点,随即该节点进入自旋状态
自旋时,首先检查前驱节点释放为头节点&tryAcquireShared()是否>=0(即成功获取同步状态)
如果是,则说明当前节点可执行,同时把当前节点设置为头节点,并且唤醒所有后继节点.如果否,则利用
LockSupport.unpark(this)
挂起当前线程,等待被前驱节点唤醒释放共享锁流程
调用releaseShared(arg)模版方法释放同步状态
如果释放成,则遍历整个队列,利用
LockSupport.unpark(nextNode.thread)
唤醒所有后继节点
独占锁和共享锁在实现上的区别
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
1、AQS(AbstranctQueueSynchronizer)核心思想是当共享资源空闲的时候,将处于队列的头节点线程启动,并且占用公共资源为锁定状态.此时其他资源访问公共资源则会被阻塞,其他线程会自旋开始等待公共资源的释放.新加入的线程如果没有获取到锁则会将新加入的线程加入到队列的尾部.对于公共资源的状态使用CAS方式实现修改,保证线程安全.
2、AQS有两种资源获取模式:
3、AQS采用了模板方法的设计模式
4、AQS整体流程
独占模式,线程加入的时候尝试获取共享资源,若获取失败则包装成Node类,并利用CAS将其放置AQS的列尾,然后开始自旋。自旋时判断前驱节点是否是头节点&是否成功获取同步状态,两个条件都成立,则将当前线程的节点设置为头节点,如果不是,则利用LockSupport.park(this)
将当前线程挂起 ,等待被前驱节点唤醒。头节点在释放锁的时候会调用release方法,释放锁后会利用LockSupport.unpark(s.thread)
唤醒下一个节点的线程。
共享模式,线程加入的时候同样会尝试获取共享资源(共享资源可同时使用数量>0)。若获取失败则包装成Node类,利用CAS将其放置队列尾部。然后开始自旋,自旋时判断前驱节点是否是头节点,如果前驱节点不是头节点则
则利用LockSupport.park(this)
将当前线程挂起。头节点在释放锁的时候会调用release方法,释放锁后会利用LockSupport.unpark(s.thread)
唤醒下一个节点的线程。如果前驱节点是头节点则一种进行自旋直到获取到共享资源。
5、AQS中断和超时
若前驱节点是头节点且已经获取了共享资源锁则将前驱节点的后续节点设置为null。否则抛出异常的方式来中断次线程。超时也类似。
容器类
ConcurrentHashMap : 侧重于Map放入或者获取的速度,而不在乎顺序
ConcurrentSkipListMap : 在乎顺序,需要对数据进行非常频繁的修改
CopyOnWriteArrayList : 任何修改操作,如add、set、remove,都会拷贝原数组,修改后替换原来的数组,通过这种防御性的方式,实现另类的线程安全。
CopyOnWriteArraySet
并发队列的实现
BlockedQueue: ArrayBlockingQueue、SynchorousQueue
同步结构
CountDownLatch 允许一个或多个线程等待某些操作完成 CountDownLatch操作的是事件
CyclicBarrier 一种辅助性的同步结构,允许多个线程等待到大某个屏障 CyclicBarrier侧重点是线程
Semaphore Java版本的信号量实现
Phaser 功能与CountDownLatch很接近,允许线程动态的注册到Phaser上面,而CountDownLatch是不能动态设置的。设计初衷是实现多个线程类似步骤、阶段场景的协调,线程注册等待屏障条件出发,进而协调彼此间的行动。
强大的Executor框架
可以创建各种不同类型的线程池,调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
ConcurrentHashMap之前已经解析过了。
ConcurrentSkipListMap 多线程下安全的有序键值对。 ConcurrentSkipListMap是通过跳表来实现的。跳表是一个链表,但是通过使用“跳跃式”查找的方式使得插入、读取数据时复杂度变成了O(logn)。
跳表(Skip List)是一种**“空间换时间”**的算法设计思想。我们来通过一个例子来看下Skip List 是如何实现查找的。
首先我们有一个有排按照重量大小排序的苹果100个(假设没有重量重复的苹果)。
如果是普通的链表要找到重量为3Kg的苹果。如果不是调表结构我们从第一个开始称重,然后第二个直到找到重量是3Kg的。
如果是跳表则会有如下结构(暂时假设层级只有2)
1kg | 2.95kg | 3,5kg | 6.7kg | ||||||
---|---|---|---|---|---|---|---|---|---|
1kg | 1.1kg | 2.95kg | … | 3Kg | 3.5kg | … | 5kg | … | 6.7kg |
即可以理解为这100个苹果中有几个苹果是明确标签的标签上写着重量,但是我们要翻开标签才能看到。那么这个时候我们找3kg的苹果 的时候我们就可以这样做先翻开第一个标签发现是1kg,那么3kg肯定在后面。然后我们翻开第二个标签一看发现是2.95。3kg还在后面,我们继续翻标签找到第一个大于3的标签,那么3kg肯定在这个标签和上一个标签之间,我们只需要测量这中间的苹果即可找到3kg的苹果。这样查询效率就大大提高了。
层数是根据一种随机算法得到的,为了不让层数过大,还会有一个最大层数MAX_LEVEL限制,随机算法生成的层数不得大于该值。
在实际程序使用中标签肯定指会向一个下一层的一个苹果。且标签肯定是一个有序的,不然我们也无法快速定位了。
Skip List的基本思想
- 跳表由很多层组成;
- 每一层都是一个有序链表;
- 对于每一层的任意结点,不仅有指向下一个结点的指针,也有指向其下一层的指针。
ConcurrentSkipListMap结构解析
// 主要结构
private static final Object BASE_HEADER = new Object(); // 最底层链表的头指针BASE_HEADER
private transient volatile HeadIndex<K, V> head; // 最上层链表的头指针head
// 普通结点 Node 定义
static final class Node<K, V> {
final K key;
volatile Object value;
volatile Node<K, V> next;
// ...
}
// 头索引结点 HeadIndex
static final class HeadIndex<K,V> extends Index<K,V> {
final int level; // 层级
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level;
}
}
// 索引结点 Index 定义
static class Index<K, V> {
final Node<K, V> node; // node指向最底层链表的Node结点
final Index<K, V> down; // down指向下层Index结点
volatile Index<K, V> right; // right指向右边的Index结点
// ...
}
Node:最底层链表中的结点,保存着实际的键值对,如果单独看底层链,其实就是一个按照Key有序排列的单链表。
HeadIndex:结点是各层链表的头结点,它是Index类的子类,唯一的区别是增加了一个
level
字段,用于表示当前链表的级别,越往上层,level值越大。Index:结点是除底层链外,其余各层链表中的非头结点(见示意图中的蓝色结点)。每个Index结点包含3个指针:
down
、right
、node
。down和right指针分别指向下层结点和后继结点,node指针指向其最底部的node结点。
// 构造方法
// 空Map.
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
// 指定比较器的构造器
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
// 从给定Map构建的构造器
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.comparator = null;
initialize();
putAll(m);
}
// 从给定SortedMap构建的构造器、并且Key的顺序与原来保持一致.
public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
this.comparator = m.comparator();
initialize();
buildFromSorted(m);
}
// 将一些字段置初始化null,然后将head指针指向新创建的HeadIndex结点。
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null),null, null, 1);
}
put
// put
public V put(K key, V value) {
if (value == null) // ConcurrentSkipListMap的Value不能为null
throw new NullPointerException();
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
Node<K,V> z; // added node
if (key == null) // key不能为空
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
// 找到插入节点位置
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) {
Object v; int c;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
z = new Node<K,V>(key, value, n);
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}
// 判断是否需要扩层
int rnd = ThreadLocalRandom.nextSecondarySeed();
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0)
++level;
Index<K,V> idx = null;
HeadIndex<K,V> h = head;
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null);
}
else { // try to grow by one level
level = max + 1; // hold in array and later pick the one to use
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])new Index<?,?>[level+1];
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
h = head;
int oldLevel = h.level;
if (level <= oldLevel) // lost race to add level
break;
HeadIndex<K,V> newh = h;
Node<K,V> oldbase = h.node;
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
if (casHead(h, newh)) {
h = newh;
idx = idxs[level = oldLevel];
break;
}
}
}
// find insertion points and splice in
splice: for (int insertionLevel = level;;) {
int j = h.level;
for (Index<K,V> q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break splice;
if (r != null) {
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
findNode(key);
break splice;
}
if (--insertionLevel == 0)
break splice;
}
if (--j >= insertionLevel && j < level)
t = t.down;
q = q.down;
r = q.right;
}
}
}
return null;
}
// 查找小于且最接近给定key的Node结点
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
Node<K,V> n = r.node;
K k = n.key;
if (n.value == null) {
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
if (cpr(cmp, key, k) > 0) { // key大于k,继续向右查找
q = r;
r = r.right;
continue;
}
}
// 已经到了level1的层、直接返回对应的Node结点
if ((d = q.down) == null)
return q.node;
// 转到下一层,继续查找(level-1层)
q = d;
r = d.right;
}
}
}
remove
在删除键值对时,不会立即执行删除,而是通过引入**“标记结点”,以“懒删除”**的方式进行,以提高并发效率。
doRemove方法首先会找到待删除的结点,在它和后继结点之间插入一个value为null的标记结点(如下图中的绿色结点),然后改变其前驱结点的指向
public V remove(Object key) {
return doRemove(key, null);
}
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) < 0)
break outer;
if (c > 0) {
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v))
break outer;
if (!n.casValue(v, null))
break;
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(key); // retry via findNode
else {
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
}
return null;
}
get
找到“小于且最接近给定key”的Node结点,然后用了三个指针:b -> n -> f,n用于定位最终查找的Key,然后顺着链表一步步向下查
public V get(Object key) {
return doGet(key);
}
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}
先了解CopyOnWrite机制
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
get
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
add
add(E e) , add(int index , E element) ,addIfAbsent(E e) , addAllAbsent(Collection extends E> c) 等添加操作
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index; //要移动的元素大小
if (numMoved == 0) //尾端插入
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
//按index分前后两部分进行拷贝
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
// 获取当前array数组,查找是否含有e元素,没有则调用addIfAbsent(e, snapshot)插入
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock(); // 锁
try {
Object[] current = getArray();//获取最新的array对象
int len = current.length;
if (snapshot != current) { //若是如上所说,在此期间array发生更改
// 检查新数组中是否含有e元素
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
//在current上进行操作
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
set
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
remove
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1; //要移动的元素个数
if (numMoved == 0) // 删除的是末尾元素
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
/** array数组的快照 */
private final Object[] snapshot;
/** 指针 */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
}
一个基于链接节点的无界线程安全FIFO(先进先出)队列。队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。
下面内容转自 https://www.jianshu.com/p/ce6108e4b2c4
--------------------- 分割线 -------------------------
一个空的ConcurrentLinkedQueue包含一个空节点(数据字段=null),队列的HEAD和TAIL指针都指向这个节点。当经过一些出入队列的操作以后,队列的结构可能类似下面
node1 -> node2 -> node3 -> node4 -> node5
HEAD tail
node1 -> node2 -> node3 -> node4 -> node5
head tail
此结构表示node1已经出列但是还保持这个next的指针指向了node2。队尾的情况是表示发生了并发
node1 <- | node2 -> node3 -> node4 -> node5
head tail
node1的next节点指向自己,这表示node1已经断开了和队列的联系,如果将node1的next设置为null则无法判断node1是否是尾节点。
上面这些情形是ConcurrentLinkedQueue在执行一些操作之后可能处于的状态,之所于允许这些看起来不够严谨的状态,是为了在并发过程中提高效率。但是不管如何,在整个生命周期内,算法保持以下属性:
--------------------- 分割线 -------------------------
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentLinkedQueue.class;
headOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("head"));
tailOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("tail"));
} catch (Exception e) {
throw new Error(e);
}
}
// 构造
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
add进栈
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
// next节点指向自身表示该节点已经出栈
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
public boolean addAll(Collection<? extends E> c) {
if (c == this)
// As historically specified in AbstractQueue#addAll
throw new IllegalArgumentException();
// Copy c into a private chain of Nodes
Node<E> beginningOfTheEnd = null, last = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (beginningOfTheEnd == null)
beginningOfTheEnd = last = newNode;
else {
last.lazySetNext(newNode);
last = newNode;
}
}
if (beginningOfTheEnd == null)
return false;
// Atomically append the chain at the tail of this collection
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, beginningOfTheEnd)) {
// Successful CAS is the linearization point
// for all elements to be added to this queue.
if (!casTail(t, last)) {
// Try a little harder to update tail,
// since we may be adding many elements.
t = tail;
if (last.next == null)
casTail(t, last);
}
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
poll出栈
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
public boolean remove(Object o) {
if (o != null) {
Node<E> next, pred = null;
for (Node<E> p = first(); p != null; pred = p, p = next) {
boolean removed = false;
E item = p.item;
if (item != null) {
if (!o.equals(item)) {
next = succ(p);
continue;
}
removed = p.casItem(item, null);
}
next = succ(p);
if (pred != null && next != null) // unlink
pred.casNext(p, next);
if (removed)
return true;
}
}
return false;
}
// next指向自己
final Node<E> succ(Node<E> p) {
Node<E> next = p.next;
return (p == next) ? head : next;
}
迭代器
private class Itr implements Iterator<E> {
/**
* Next node to return item for.
*/
private Node<E> nextNode;
/**
* nextItem holds on to item fields because once we claim
* that an element exists in hasNext(), we must return it in
* the following next() call even if it was in the process of
* being removed when hasNext() was called.
*/
private E nextItem;
/**
* Node of the last returned item, to support remove.
*/
private Node<E> lastRet;
Itr() {
advance();
}
/**
* Moves to next valid node and returns item to return for
* next(), or null if no such.
*/
private E advance() {
lastRet = nextNode;
E x = nextItem;
Node<E> pred, p;
if (nextNode == null) {
p = first();
pred = null;
} else {
pred = nextNode;
p = succ(nextNode);
}
for (;;) {
if (p == null) {
nextNode = null;
nextItem = null;
return x;
}
E item = p.item;
if (item != null) {
nextNode = p;
nextItem = item;
return x;
} else {
// skip over nulls
Node<E> next = succ(p);
if (pred != null && next != null)
pred.casNext(p, next);
p = next;
}
}
}
public boolean hasNext() {
return nextNode != null;
}
public E next() {
if (nextNode == null) throw new NoSuchElementException();
return advance();
}
public void remove() {
Node<E> l = lastRet;
if (l == null) throw new IllegalStateException();
// rely on a future traversal to relink.
l.item = null;
lastRet = null;
}
}
阻塞队列。一个接口继承Queue。实现类
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque,SynchronousQueue。
有界队列,从构造方法和属性中可以看出来
final Object[] items;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
增、删
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
源码分析就不写很多了,有兴趣的可以自己研究下或者取网上找有很多。这里直接给结论:
创建ArrayBlockingQueue时指定队列大小(有的人也叫做缓存区),因为使用的是Object数组(环形数组)实现因此
建之后就无法改变队列大小。在入队的时候先使用ReentrantLock.lock()
获取锁,然后在在进行入队操作,
一旦队列已满则入队失败,否则正常入队。因此可以看出来ArrayBlockingQueue是一个线程安全的阻塞队列。
入队时会修改队列的count
putIndex
属性,然后释放锁。出队的时候也同样先获取锁。
然后直接对takeIndex
位置的元素进行出队操作,改变count
takeIndex
。删除操作就相对麻烦一些,
需要判断删除的节点是否时头节点,若是则很简单直接将头节点设置为null,然后减少count
使用迭代器
变更队列中的数据顺序,若不是头节点则需要将被删除节点后的所有节点左移。
API方法:
boolean |
add(E e) 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则抛出 IllegalStateException 。 |
---|---|
void |
clear() 自动移除此队列中的所有元素。 |
boolean |
contains(Object o) 如果此队列包含指定的元素,则返回 true 。 |
int |
drainTo(Collection super E> c) 移除此队列中所有可用的元素,并将它们添加到给定 collection 中。 |
int |
drainTo(Collection super E> c, int maxElements) 最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。 |
Iterator |
iterator() 返回在此队列中的元素上按适当顺序进行迭代的迭代器。 |
boolean |
offer(E e) 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则返回 false 。 |
boolean |
offer(E e, long timeout, TimeUnit unit) 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。 |
E |
peek() 获取但不移除此队列的头;如果此队列为空,则返回 null 。 |
E |
poll() 获取并移除此队列的头,如果此队列为空,则返回 null 。 |
E |
poll(long timeout, TimeUnit unit) 获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 |
void |
put(E e) 将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。 |
int |
remainingCapacity() 返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。 |
boolean |
remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。 |
int |
size() 返回此队列中元素的数量。 |
E |
take() 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。 |
Object[] |
toArray() 返回一个按适当顺序包含此队列中所有元素的数组。 |
|
toArray(T[] a) 返回一个按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 |
String |
toString() 返回此 collection 的字符串表示形式。 |
private final transient ReentrantLock lock = new ReentrantLock();
// 优先队列,用于存储元素,并按优先级排序
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 用于优化内部阻塞通知的线程
private Thread leader = null;
// 用于实现阻塞的Condition对象
private final Condition available = lock.newCondition();
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
// 获取全局独占锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
// 获取队首元素
E first = q.peek();
// 队首为空,则阻塞当前线程
if (first == null)
available.await();
else {
// 获取队首元素的超时时间
long delay = first.getDelay(NANOSECONDS);
// 已超时,直接出队
if (delay <= 0)
return q.poll();
// 释放first的引用,避免内存泄漏
first = null; // don't retain ref while waiting
// leader != null表明有其他线程在操作,阻塞当前线程
if (leader != null)
available.await();
else {
// leader指向当前线程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 超时阻塞
available.awaitNanos(delay);
} finally {
// 释放leader
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// leader为null并且队列不为空,说明没有其他线程在等待,那就通知条件队列
if (leader == null && q.peek() != null)
available.signal();
// 释放全局独占锁
lock.unlock();
}
}
LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
private final int capacity;
private final AtomicInteger count = new AtomicInteger();
transient Node<E> head;
private transient Node<E> last;
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
从上面的属性我们知道,每个添加到LinkedBlockingQueue队列中的数据都将被封装成Node节点,添加的链表队列中,其中head和last分别指向队列的头结点和尾结点。与ArrayBlockingQueue不同的是,LinkedBlockingQueue内部分别使用了takeLock 和 putLock 对并发进行控制,也就是说,添加和删除操作并不是互斥操作,可以同时进行,这样也就可以大大提高吞吐量。如果不指定队列的容量大小,也就是使用默认的Integer.MAX_VALUE,如果存在添加速度大于删除速度时候,有可能会内存溢出,这点在使用前希望慎重考虑。另外,LinkedBlockingQueue对每一个lock锁都提供了一个Condition用来挂起和唤醒其他线程。
源码阅读略
API
boolean |
add(E e) 在不违反容量限制的情况下,将指定的元素插入此双端队列的末尾。 |
---|---|
void |
addFirst(E e) 如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的开头;如果当前没有空间可用,则抛出 IllegalStateException 。 |
void |
addLast(E e) 如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的末尾;如果当前没有空间可用,则抛出 IllegalStateException 。 |
void |
clear() 以原子方式 (atomically) 从此双端队列移除所有元素。 |
boolean |
contains(Object o) 如果此双端队列包含指定的元素,则返回 true 。 |
Iterator |
descendingIterator() 返回在此双端队列的元素上以逆向连续顺序进行迭代的迭代器。 |
int |
drainTo(Collection super E> c) 移除此队列中所有可用的元素,并将它们添加到给定 collection 中。 |
int |
drainTo(Collection super E> c, int maxElements) 最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。 |
E |
element() 获取但不移除此双端队列表示的队列的头部。 |
E |
getFirst() 获取,但不移除此双端队列的第一个元素。 |
E |
getLast() 获取,但不移除此双端队列的最后一个元素。 |
Iterator |
iterator() 返回在此双端队列元素上以恰当顺序进行迭代的迭代器。 |
boolean |
offer(E e) 如果立即可行且不违反容量限制,则将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),并在成功时返回 true ;如果当前没有空间可用,则返回 false 。 |
boolean |
offer(E e, long timeout, TimeUnit unit) 将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),必要时将在指定的等待时间内一直等待可用空间。 |
boolean |
offerFirst(E e) 如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的开头,并在成功时返回 true ;如果当前没有空间可用,则返回 false 。 |
boolean |
offerFirst(E e, long timeout, TimeUnit unit) 将指定的元素插入此双端队列的开头,必要时将在指定的等待时间内等待可用空间。 |
boolean |
offerLast(E e) 如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的末尾,并在成功时返回 true ;如果当前没有空间可用,则返回 false 。 |
boolean |
offerLast(E e, long timeout, TimeUnit unit) 将指定的元素插入此双端队列的末尾,必要时将在指定的等待时间内等待可用空间。 |
E |
peek() 获取但不移除此双端队列表示的队列的头部(即此双端队列的第一个元素);如果此双端队列为空,则返回 null 。 |
E |
poll() 获取并移除此双端队列表示的队列的头部(即此双端队列的第一个元素);如果此双端队列为空,则返回 null 。 |
E |
poll(long timeout, TimeUnit unit) 获取并移除此双端队列表示的队列的头部(即此双端队列的第一个元素),如有必要将在指定的等待时间内等待可用元素。 |
E |
pollLast() 获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null 。 |
E |
pollLast(long timeout, TimeUnit unit) 获取并移除此双端队列的最后一个元素,必要时将在指定的等待时间内等待可用元素。 |
E |
pop() 从此双端队列所表示的堆栈中弹出一个元素。 |
void |
push(E e) 将元素推入此双端队列表示的栈。 |
void |
put(E e) 将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),必要时将一直等待可用空间。 |
int |
remainingCapacity() 返回理想情况下(没有内存和资源约束)此双端队列可不受阻塞地接受的额外元素数。 |
E |
remove() 获取并移除此双端队列表示的队列的头部。 |
boolean |
remove(Object o) 从此双端队列移除第一次出现的指定元素。 |
E |
removeFirst() 获取并移除此双端队列第一个元素。 |
boolean |
removeFirstOccurrence(Object o) 从此双端队列移除第一次出现的指定元素。 |
E |
removeLast() 获取并移除此双端队列的最后一个元素。 |
boolean |
removeLastOccurrence(Object o) 从此双端队列移除最后一次出现的指定元素。 |
int |
size() 返回此双端队列中的元素数。 |
E |
take() 获取并移除此双端队列表示的队列的头部(即此双端队列的第一个元素),必要时将一直等待可用元素。 |
E |
takeFirst() 获取并移除此双端队列的第一个元素,必要时将一直等待可用元素。 |
E |
takeLast() 获取并移除此双端队列的最后一个元素,必要时将一直等待可用元素。 |
Object[] |
toArray() 返回以恰当顺序(从第一个元素到最后一个元素)包含此双端队列所有元素的数组。 |
|
toArray(T[] a) 返回以恰当顺序包含此双端队列所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 |
String |
toString() 返回此 collection 的字符串表示形式。 |
参考资料:https://www.cnblogs.com/dwlsxj/p/Thread.html
不像ArrayBlockingQueue或LinkedListBlockingQueue,SynchronousQueue内部并没有数据缓存空间,你不能调用peek()方法来看队列中是否有数据元素,因为数据元素只有当你试着取走的时候才可能存在,不取走而只想偷窥一下是不行的,当然遍历这个队列的操作也是不允许的。队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。数据是在配对的生产者和消费者线程之间直接传递的,并不会将数据缓冲数据到队列中。可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。
1、不能在同步队列上进行 peek,因为仅在试图要取得元素时,该元素才存在;
2、除非另一个线程试图移除某个元素,否则也不能(使用任何方法)添加元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的头是尝试添加到队列中的首个已排队线程元素; 如果没有已排队线程,则不添加元素并且头为 null。
3、对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空集合。此队列不允许 null 元素。
4、它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
5、对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。 但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。 公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。
6、SynchronousQueue的以下方法:* iterator() 永远返回空,因为里面没东西。 * peek() 永远返回null。 * put() 往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走。 * offer() 往queue里放一个element后立即返回,如果碰巧这个element被另一个thread取走了,offer方法返回true,认为offer成功;否则返回false。 * offer(2000, TimeUnit.SECONDS) 往queue里放一个element但是等待指定的时间后才返回,返回的逻辑和offer()方法一样。 * take() 取出并且remove掉queue里的element(认为是在queue里的。。。),取不到东西他会一直等。 * poll() 取出并且remove掉queue里的element(认为是在queue里的。。。),只有到碰巧另外一个线程正在往queue里offer数据或者put数据的时候,该方法才会取到东西。否则立即返回null。 * poll(2000, TimeUnit.SECONDS) 等待指定的时间然后取出并且remove掉queue里的element,其实就是再等其他的thread来往里塞。 * isEmpty()永远是true。 * remainingCapacity() 永远是0。 * remove()和removeAll() 永远是false。
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
static class WaitQueue implements java.io.Serializable { }
static class LifoWaitQueue extends WaitQueue {
private static final long serialVersionUID = -3633113410248163686L;
}
static class FifoWaitQueue extends WaitQueue {
private static final long serialVersionUID = -3623113410248163686L;
}
private ReentrantLock qlock;
private WaitQueue waitingProducers;
private WaitQueue waitingConsumers;
private transient volatile Transferer<E> transferer;
TransferQueue持有节点类QNode(持有next节点的引用),TransferQueue本身还有head和tai属性。其方法是使用CAS是实现的。
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
if (transferer.transfer(e, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // constructed/reused as needed
// put操作这里e不为null,isData为true
boolean isData = (e != null);
// 自旋
for (;;) {
QNode t = tail;
QNode h = head;
// 还没有初始化,则continue重新来过
if (t == null || h == null) // saw uninitialized value
continue; // spin
// h == t则说明还没有存入元素。这是为true,进入。进入这个if就会堵塞元素,不管take还是put。
if (h == t || t.isData == isData) { // empty or same-mode
// tn为null。(顺便说一句:next变量是volatile修饰,多线程可见,所以下面这个复制操作是线程安全的。)
QNode tn = t.next;
// 其他线程把尾节点改变了,则再旋一次
if (t != tail) // inconsistent read
continue;
// tn为null
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
// 无超时机制
if (timed && nanos <= 0) // can't wait
return null;
// 创建put操作存放数据的新节点,isData为true。
// 如果没有元素,take操作进入这里,也创建了一个新node,也就是说take操作堵塞的时候,也会创建节点,
if (s == null)
s = new QNode(e, isData);
// cas替换,从:head(tail)(dummy)到head(tail)(dumy)->S
// cas替换,尾节点的next指向新建立的s节点。失败则再旋一次
if (!t.casNext(null, s)) // failed to link in
continue;
/*cas替换,把TransferQuene的旧尾节点t替换为新节点s。
至此,变成了head(dumy)=>s(tail)
*/
advanceTail(t, s); // swing tail and wait
// 下面这个方法调用就是具体的堵塞调用,看下一段代码分析
Object x = awaitFulfill(s, e, timed, nanos);
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
if (!s.isOffList()) { // not already unlinked
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
return (x != null) ? (E)x : e;
} else { // complementary-mode
QNode m = h.next; // node to fulfill
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
advanceHead(h, m); // successfully fulfilled
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
// s是新节点,e是put的真正数据,timed是是否超时,nanos是超时时间
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
// deadLine为0
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
// 自旋次数,第一次put的时候,链表结构为:head(dumy)=>s(tail),所以
// head.hext == s,timed为不超时false,所以spin=maxUntimedSpins=512,
// 如果第二个put堵塞,则结构为:head(dumy)=>s=>s1(tail),而head.next和s1(新put的元素是s1)不相等,所以,spin=0直接堵塞。
//(为什么会自旋,我估计是为了,高并发下,take操作
// 和put操作很可能再极短时间进行,这样的话,就不需要唤醒线程和堵塞线程)
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 当前线程准备中断,则s节点的item指向s节点自己
if (w.isInterrupted())
s.tryCancel(e);
Object x = s.item;
// 这里为false,只有当其他线程把这个元素替换了,比如put堵塞在这
// 里的时候,take就会把这个元素替换掉,然后put唤醒的时候就能直接return了。
if (x != e)
return x;
if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
s.tryCancel(e);
continue;
}
}
// 这里spins初始为512,一直递减到0
if (spins > 0)
--spins;
else if (s.waiter == null)
// node节点和waiter和当前线程关联上,为了公平的唤醒。
s.waiter = w;
else if (!timed)
// 锁住当前线程。设置thread的block变量为parkBlocker指向的对象transferQueue。
LockSupport.park(this);
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
public E take() throws InterruptedException {
E e = transferer.transfer(null, false, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // constructed/reused as needed
// isData为false
boolean isData = (e != null);
for (;;) {
QNode t = tail;
QNode h = head;
if (t == null || h == null) // saw uninitialized value
continue; // spin
// 第一put后结构变成了:head(dumy)=>s(tail)
/* 所以这里h不等于t,t.isData为true,
*所以这里不成立,走else块。只要不堵塞,都会走else块。put操作如
果不堵塞,也会走else块。
*/
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
if (t != tail) // inconsistent read
continue;
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
if (timed && nanos <= 0) // can't wait
return null;
if (s == null)
s = new QNode(e, isData);
if (!t.casNext(null, s)) // failed to link in
continue;
advanceTail(t, s); // swing tail and wait
Object x = awaitFulfill(s, e, timed, nanos);
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
if (!s.isOffList()) { // not already unlinked
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
return (x != null) ? (E)x : e;
} else { // complementary-mode
//head(dumy)=>s(tail)
// m就是之前put的哪个节点
QNode m = h.next; // node to fulfill
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
/*
isData=(e!=null),这是take操作,所以e为null,所以isData为false,x为之前put进去的值,为非null
x == m说明已经取消了,之前put操作的时候,
*awaitFulfill方法里,如果当前线程准备中断,
*就会调用qnode的tryCancel方法,让qnode的next指向自己,代表
* 这个节点取消了。
head(dumy)=>s(tail)
*!m.casItem(x, e):直接替换item的值,
这样,在take方法堵塞在awaitFulfill方法里的时候,
这里直接把之前take方法创建的node的item改掉,
然后唤醒take的线程,然后take操作获取到这个新值了和它之前的值不一样,则直接跳出循环,不堵塞。
*/
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
// 变成了head(s)(tail)
advanceHead(h, m); // successfully fulfilled
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
SynchronousQueue还有offer方法,poll方法。add方法。
offer方法,不会堵塞,可以存进去,则存进去(poll或take操作正在堵塞等着获取元素),否则,直接返回false。
poll方法可以有超时时间,和take差不多,take没有超时时间。
add方法,调用的就是offer方法,不过add方法,添加不进去,则直接报错。
对ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue进行下小结
ArrayBlockingQueue:
基于数组实现
final Object[] items
,因为是基于数组实现所以是有界队列,在初始化的时候就确定了大小。数组是一个“环形”的数组,头尾相接。在入队操作中使用了ReentrantLock.lock()
获取锁,然后在在进行入队操作,一旦队列已满则入队失败,否则正常入队。出队也是一样通过ReentrantLock.lock()
获取锁。内部使用了俩个属性takeIndex
putIndex
分别对应数组入队和出队的时候所在的数组位置count
为队列元素数量。提供了入队、等待时间内入队、出队、等待时间内出队、查看队列头部元素等操作。LinkedBlockingQueue:
基于链表的阻塞队列,容量是无限的,当然在构造实例的时候可以设置上限。本身持有
head
last
节点表示链表的头元素和尾元素。提供了俩个锁putLock
takeLock
这意味着LinkedBlockingQueue的入队和出队操作可以并行进行。入队的时候获取putLock
然后锁定入队操作,进行入队操作。出队的时候同样获取出队锁takeLock
。注意的是其他操作都会获取两个锁然后在进行相关操作。SynchronousQueue:
没有数据缓存空间(没有数组属性或者链表结构)。因此无法支持查看队列头部信息的方法。任何一个对SynchronousQueue写需要等到一个对SynchronousQueue的读操作,反之亦然。一个读操作需要等待一个写操作,相当于是交换通道,提供者和消费者是需要组队完成工作,缺少一个将会阻塞线程,直到等到配对为止。分为公平模式和非公平模式。公平模式即是先进先出的方式。内部类
TransferQueue WaitQueue
。take和put操作都是使用内部类实现的。如何实现公平锁和非公平锁可以查看资料。
countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
// 构造器可指定容器含量
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 主要方法
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
初始化的时候传入正数然后计数器按照这个正数进行计数。内部类Sync
继承AQS
重写了tryAcquireShared
和tryReleaseShared
方法,这说明countDownLatch使用的共享锁。
内部类Sync同样继承AQS;
AQS的state代表count;
初始化使用计数器count;
count代表多个线程执行或者某个操作执行次数;
countDown()方法将会将count-1;
count为0将会释放所有等待线程;
await方法将会阻塞直到count为0;
CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
count不为0,但是等待时间过去将会返回false。
开关锁应用;
问题分解应用–并行性;
参考资料:https://www.cnblogs.com/liuyun1995/p/8529360.html
// 属性及构造
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
//同步操作锁
private final ReentrantLock lock = new ReentrantLock();
//线程拦截器
private final Condition trip = lock.newCondition();
//每次拦截的线程数
private final int parties;
//换代前执行的任务
private final Runnable barrierCommand;
//表示栅栏的当前代
private Generation generation = new Generation();
//计数器
private int count;
//静态内部类Generation
private static class Generation {
boolean broken = false;
}
CyclicBarrier并没有直接使用AQS来定义自己的Sync同步辅助类。但是其使用的ReentrantLock来作为锁使用。CyclicBarrier是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。CyclicBarrier在涉及一定大小的线程的程序而这些线程有时必须彼此等待的情况下很有用。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
解释下构造中参数的含义:
corePoolSize:核心线程数,如果运行的线程少于corePoolSize,则创建新线程来执行新任务,即使线程池中的其他线程是空闲的
maximumPoolSize:最大线程数,可允许创建的线程数,corePoolSize和maximumPoolSize设置的边界自动调整池大小
keepAliveTime:如果线程数多于corePoolSize,则这些多余的线程的空闲时间超过keepAliveTime时将被终止
unit:keepAliveTime参数的时间单位
workQueue:保存任务的阻塞队列,与线程池的大小有关:
当运行的线程数少于corePoolSize时,在有新任务时直接创建新线程来执行任务而无需再进队列
当运行的线程数等于或多于corePoolSize,在有新任务添加时则选加入队列,不直接创建线程
当队列满时,在有新任务时就创建新线程
threadFactory:使用ThreadFactory创建新线程,默认使用defaultThreadFactory创建线程
handler:定义处理被拒绝任务的策略,默认使用ThreadPoolExecutor.AbortPolicy,任务被拒绝时将抛出RejectExecutorException
ThreadPoolExecutor有一个核心线程池和一个最大线程池,核心线程池内的线程不会因为取不到任务而销毁。
提交任务时会先尝试添加核心线程,如果核心线程池没满就添加新的核心线程。不管创建过的核心线程有没有空闲,只要来了任务都会创建新的核心线程,直到核心线程数达到最大值。当核心线程达到最大值时,再提交任务就会将任务提交到阻塞队列中,阻塞队列有多种实现,java的Executors工具类提供了三种ThreadPoolExecutor的构造:使用LinkedBlockingQueue,核心线程和最大线程数一样的FixedThreadPool;使用LinkedBlockingQueue,核心线程和最大线程数都是1的SingleThreadExecutor;使用SynchronousQueue,核心线程是0、最大线程数都是integer最大值的CachedThreadPool,当阻塞队列已满,不能存放新的任务时,就会在最大线程池内创建会销毁的普通线程。不管是核心线程还是普通线程都会在创建后先执行创建时放入的任务,执行完后再从阻塞队列获取任务。当最大线程池也达到最大值时,会触发拒绝策略,拒绝策略可自定义,java提供的有:什么都不做、直接抛异常、阻塞队列抛弃第一个任务后将新任务再提交、由提交任务线程直接执行。
属性
// 存放了worker总数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// 阻塞队列,存放任务
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet<Worker>();
// hashset存放worker
private final Condition termination = mainLock.newCondition();
// 当前线程池达到过的最大worker数
private int largestPoolSize;
// 线程池处理过的任务总数,只有在worker中断的时候才会将该worker处理的任务总数加入completedTaskCount
private long completedTaskCount;
// 用于产生worker的工厂类
private volatile ThreadFactory threadFactory;
// 拒绝策略,在线程池处理不了任务时调用
private volatile RejectedExecutionHandler handler;
// worker的最大空闲时间,超过这个时间没取到任务会销毁worker
private volatile long keepAliveTime;
// 是否允许核心线程超时,默认为false,可以调用allowCoreThreadTimeOut修改值
private volatile boolean allowCoreThreadTimeOut;
// 最大核心线程数
private volatile int corePoolSize;
// 最大线程数,corePoolSize计算在内
private volatile int maximumPoolSize;
// 默认的拒绝策略,抛出异常
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread");
// boolean值,用于只中断一个worker
private static final boolean ONLY_ONE = true;
对外暴漏方法
Executor接口就一个方法executor,入参时一个Runnable的实现类,用于提交任务。ThreadPoolExecutor重写了executor方法。此外,ThreadPoolExecutor的继承了AbstractExecutorService类,AbstractExecutorService提供了可提交Callable任务的submit方法,submit将任务包装成一个FutureTask返回,调用方可根据返回值获取执行结果。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果工作线程数小于核心线程数最大值,则创建新的核心线程处理本次任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//核心线程数已满,则将任务加入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//再次检查线程池运行状态,如果已经停止运行则将任务删除,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//如果线程数为0,则补充工作线程,添加的新工作线程不会处理本次任务,会直接从阻塞队列取
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果阻塞队列满了,则添加新的工作线程处理本次任务
else if (!addWorker(command, false))
//工作线程达到最大值则执行拒绝策略
reject(command);
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
工作线程worker源码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
//线程对象,构造worker时将thread赋值为自己
//启动worker,就是通过这个thread调用自己的run方法
final Thread thread;
//第一个任务,也就是创建该工作线程时外部提交的任务
Runnable firstTask;
//worker处理完成的任务数
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//thread对象创建时,可以放入一个runnable,此处放的就是worker自己
this.thread = getThreadFactory().newThread(this);
}
public void run() {
//worker处理任务的核心代码
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
一个工具类来获取线程池的。
https://blog.csdn.net/fenglllle/article/details/83478382
package com.prc.threadDemo1.threadPoolPrc;
import java.util.concurrent.*;
/**
* 线程池使用练习
*/
public class ThreadPoolPrc {
public static void main(String[] args) {
// 验证是否是单例
ThreadPoolExecutor t1 = ThreadPoolUtil.getThreadPool();
ThreadPoolExecutor t2 = ThreadPoolUtil.getThreadPool();
System.out.println(t1.equals(t2));
ThreadPoolUtil.execute(() ->{
System.out.println("1");
});
ThreadPoolPrc tp1 = new ThreadPoolPrc();
tp1.ExecutorProduce();
}
// Executors 获取线程池
public void ExecutorProduce(){
// 没有额外线程,只存在核心线程,而且核心线程没有超时机制,而且任务队列没有长度的限制
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i =1; i<=20; i++){
final int index=i ;
pool.execute(new Runnable(){
@Override
public void run() {
try {
System.out.println("第" +index + "个线程" +Thread.currentThread().getName());
Thread.sleep(1000);
} catch(InterruptedException e ) {
e .printStackTrace();
}
}
});
}
// 线程池关闭
pool.shutdown();
// ScheduledThreadPool
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println("延迟1秒后每三秒执行一次");
}
},1,3,TimeUnit.SECONDS);
scheduledThreadPool.shutdown();
}
}
package com.prc.threadDemo1.threadPoolPrc;
import java.util.concurrent.*;
/**
* 自定义线程池工具类
*/
public class ThreadPoolUtil {
/* 私有化构造方法 */
private ThreadPoolUtil() {super();}
//**************** 线程池参数 ***************************//
// 核心线程池数
private final static Integer COREPOOLSIZE = 2;
// 最大线程数数量
private final static Integer MAXIMUMPOOLSIZE = 4;
// 线程存活时间
private final static Integer KEEPALIVETIME = 60;
// 时间单位秒
private final static TimeUnit unit = TimeUnit.SECONDS;
// 是否允许核心线程超时,默认为false
private final static boolean ALLOWCORETHREADTIMEOUT = false;
// 线程等待队列
private static BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);
// 验证是否单例使用
public static ThreadPoolExecutor getThreadPool() {
return threadPool;
}
// 线程池对象,使用static保证调用的是同一个线程池实例
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE,
KEEPALIVETIME, unit, queue, new ThreadPoolExecutor.AbortPolicy());
static {
threadPool.allowCoreThreadTimeOut(ALLOWCORETHREADTIMEOUT);
}
//******************** 对外提供方法 ***********************//
/**
* 返回future值用来获取调用线程池返回的结果
* @param c Callable接口实现类实例
* @return
*/
public static Future<?> submit(Callable<?> c) {
return threadPool.submit(c);
}
/**
* 不关心返回结果
* @param r
*/
public static void execute(Runnable r) {
threadPool.execute(r);
}
/**
* 获取当前线程池线程数量
* @return
*/
public static int getSize() {
return threadPool.getPoolSize();
}
/**
* 获取当前活动的线程数量
* @return
*/
public static int getActiveCount() {
return threadPool.getActiveCount();
}
}
可归纳为几类
容器类 :ConcurrentHashMap、ConcurrentSkipListMap、CopyOnWriteArrayList、CopyOnWriteArraySet
队列 :ConcurrentLinkedQueue、BlockingQueue
同步结构 :countDownLatch、CyclicBarrier
Executor框架
挑重点说
ThreadPoolExecutor
主要属性:
核心线程数 : 核心线程数 最大线程数 : 最大线程数 线程存活时间 :一般指的是非核心线程,当然核心线程也可以通过设置来决定是否有存活时间
线程工厂 : 用来生产线程的工厂类
拒绝任务的策略:线程池满的时候的拒绝策略。默认是返回异常。
阻塞队列
额外扩展学习了跳表的数据结构、CopyOnWrite机制
本章内容为知识点的理论学习,后续章节学习涉及的一些框架思想并进行实践练习。