我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
文章目录
- 一、架构
- 1.1 常见子类 UML
- 二、 Queue 接口
- 三、 AbstractQueue 抽象类
- 3.1 代码架构
- 3.2 方法列表
- 3.3 通用方法区别
- 四、BlockingQueue 接口
- 4.1 代码架构
- 4.2 方法列表
- 五 、子类 LinkedBlockingQueue
- 5.1 变量
- 5.2 方法
- 5.3 static 内部类 Node
- 5.4 private 内部类 Itr
- 5.5 静态内部类LBQSpliterator
- 5.6 实战 Executors
- 5.6.1 Executors#newFixedThreadPool
- 5.6.2 Executors#newSingleThreadExecutor
- 六、子类 ArrayBlockingQueue
- 6.1 变量
- 6.2 方法
- 6.3 Demo
- 6.4 ArrayBlockingQueue 与 LinkedBlockingQueue区别
- 七、 子类 SynchronousQueue
- 7.1 变量
- 7.2 方法
- 7.3 Demo
- 7.4 实战 Executors
- 7.4.1 Executors#newCachedThreadPool
- 八、 子类 PriorityBlockingQueue
- 8.1 demo01(不允许未实现Comparable接口的对象)
- 8.2 demo02
- 九、 子类 DelayQueue
- 9.1 Demo
- 9.2 Demo2
- 十、番外篇
package java.util;
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
=== 点击查看top目录 ===
package java.util;
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
implements Queue<E> {
protected AbstractQueue() {}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public void clear() {
while (poll() != null)
;
}
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
}
方法 | 备注 |
---|---|
add | 添加元素,内部是offer 推进去 |
addAll | 添加一整个Collection的元素 |
remove | 删除元素,内部是 poll 拉出来 |
element | 拿出一个Element ,不remove |
clear | 清除元素,内部死循环poll |
若queue是空的:remove 抛出异常,take 循环直到拿到元素,poll 返回null
若queue是空的:peek返回null
若queue是满的:add 抛出异常,offer返回 false,put循环直到能塞入元素
=== 点击查看top目录 ===
package java.util.concurrent;
import java.util.Collection;
import java.util.Queue;
public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements);
}
方法 | 备注 |
---|---|
add | - |
offer | - |
put | 队列满了,阻塞等一会塞进去 |
offer | 队列满了,阻塞等一段时间塞进去,时间过了queue还没位置塞入就退出了 |
take | 队列空了,阻塞等待领取 |
poll | 队列空了,阻塞等一段时间,时间过了queue还没元素拿出就退出了 |
remainingCapacity | 查看 queue 剩余多少容量 |
remove | 移除元素 |
contains | 是否包含某个元素 |
drainTo | 一个 Collection 倒到另一个 |
=== 点击查看top目录 ===
从 tail 插进去,从 head 取出来
内部是链表结构
LinkedBlockingQueue 既继承了 AbstractQueue抽象类又实现了 BlockingQueue
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
=== 点击查看top目录 ===
变量 | 备注 |
---|---|
private final int capacity | queue 容量,默认Integer.MAX_VALUE,注意是 final类型 |
private final AtomicInteger count | queue内数量,CAS |
transient Node head; | queue头 ,注意 transient |
private transient Node last; | queue的尾巴,注意 transient |
private final ReentrantLock takeLock | 取锁take锁 |
private final Condition notEmpty = takeLock.newCondition(); | queue非空Condition |
private final ReentrantLock putLock | 塞锁put锁 |
private final Condition notFull = putLock.newCondition(); | queue非满Condition |
=== 点击查看top目录 ===
方法 | 备注 |
---|---|
public int size() | CAS获取queue数量 |
public int remainingCapacity() | queue 剩余容量 |
public void put(E e) throws InterruptedException | blocking 等待 put 进去元素 |
public boolean offer(E e, long timeout, TimeUnit unit) | blocking put 进去元素 尝试一段时间 |
public boolean offer(E e) | blocking到死,一定要put进去 |
public E take() throws InterruptedException | blocking 等待获取元素 |
public E poll(long timeout, TimeUnit unit) | blocking 等待获取元素(尝试一段时间) |
public E poll() | blocking到死,一定要 poll 出来 |
public E peek() | 拿到queue的队头 first 的Element |
public boolean remove(Object o) | 先 fullyLock ,然后删掉某个元素 |
public boolean contains(Object o) | 先 fullyLock ,在查看元素是否存在 |
public Object[] toArray() | 先 fullyLock ,再转成数组Array |
public T[] toArray(T[] a) | 先 fullyLock ,再转成数组Array 放到 T[] a 里面去 |
public void clear() | 先 fullyLock,再把内部元素给清空了 |
public int drainTo(Collection super E> c, int maxElements) | 先 takeLock,从this中倒出来maxElements 个元素到c里面去。 |
private void writeObject(java.io.ObjectOutputStream s) | 把对象写到输出流,内部重写 |
private void readObject(java.io.ObjectInputStream s) | 把输入流变成对象,内部重写 |
public Iterator iterator() | 得到内部类 Itr |
public Spliterator spliterator() | 得到内部类 new LBQSpliterator(this) |
=== 点击查看top目录 ===
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
=== 点击查看top目录 ===
private class Itr implements Iterator<E> {
private Node<E> current;
private Node<E> lastRet;
private E currentElement;
Itr() {
fullyLock();
try {
current = head.next;
if (current != null)
currentElement = current.item;
} finally {
fullyUnlock();
}
}
public boolean hasNext() {
return current != null;
}
private Node<E> nextNode(Node<E> p) {
for (;;) {
Node<E> s = p.next;
if (s == p)
return head.next;
if (s == null || s.item != null)
return s;
p = s;
}
}
public E next() {
fullyLock();
try {
if (current == null)
throw new NoSuchElementException();
E x = currentElement;
lastRet = current;
current = nextNode(current);
currentElement = (current == null) ? null : current.item;
return x;
} finally {
fullyUnlock();
}
}
public void remove() {
if (lastRet == null)
throw new IllegalStateException();
fullyLock();
try {
Node<E> node = lastRet;
lastRet = null;
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (p == node) {
unlink(p, trail);
break;
}
}
} finally {
fullyUnlock();
}
}
}
=== 点击查看top目录 ===
static final class LBQSpliterator<E> implements Spliterator<E> {
static final int MAX_BATCH = 1 << 25; // max batch array size;
final LinkedBlockingQueue<E> queue;
Node<E> current; // current node; null until initialized
int batch; // batch size for splits
boolean exhausted; // true when no more nodes
long est; // size estimate
LBQSpliterator(LinkedBlockingQueue<E> queue) {
this.queue = queue;
this.est = queue.size();
}
public long estimateSize() { return est; }
public Spliterator<E> trySplit() {
Node<E> h;
final LinkedBlockingQueue<E> q = this.queue;
int b = batch;
int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1;
if (!exhausted &&
((h = current) != null || (h = q.head.next) != null) &&
h.next != null) {
Object[] a = new Object[n];
int i = 0;
Node<E> p = current;
q.fullyLock();
try {
if (p != null || (p = q.head.next) != null) {
do {
if ((a[i] = p.item) != null)
++i;
} while ((p = p.next) != null && i < n);
}
} finally {
q.fullyUnlock();
}
if ((current = p) == null) {
est = 0L;
exhausted = true;
}
else if ((est -= i) < 0L)
est = 0L;
if (i > 0) {
batch = i;
return Spliterators.spliterator
(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL |
Spliterator.CONCURRENT);
}
}
return null;
}
public void forEachRemaining(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
final LinkedBlockingQueue<E> q = this.queue;
if (!exhausted) {
exhausted = true;
Node<E> p = current;
do {
E e = null;
q.fullyLock();
try {
if (p == null)
p = q.head.next;
while (p != null) {
e = p.item;
p = p.next;
if (e != null)
break;
}
} finally {
q.fullyUnlock();
}
if (e != null)
action.accept(e);
} while (p != null);
}
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
final LinkedBlockingQueue<E> q = this.queue;
if (!exhausted) {
E e = null;
q.fullyLock();
try {
if (current == null)
current = q.head.next;
while (current != null) {
e = current.item;
current = current.next;
if (e != null)
break;
}
} finally {
q.fullyUnlock();
}
if (current == null)
exhausted = true;
if (e != null) {
action.accept(e);
return true;
}
}
return false;
}
public int characteristics() {
return Spliterator.ORDERED | Spliterator.NONNULL |
Spliterator.CONCURRENT;
}
}
=== 点击查看top目录 ===
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
=== 点击查看top目录 ===
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
变量 | 备注 |
---|---|
final Object[] items; | 存储数据的数组 |
int takeIndex; | take 的下标 |
int putIndex; | put 的下标 |
int count; | Queue中的元素数量 |
final ReentrantLock lock; | 锁,存+放共用一个锁 |
private final Condition notEmpty; | 未空Condition,表示可以取 take |
private final Condition notFull; | 未满Condition,表示可以塞 put |
transient Itrs itrs = null; | 遍历用的迭代器 |
=== 点击查看top目录 ===
方法 | 备注 |
---|---|
public boolean add(E e) | 内部调用 offer |
public boolean offer(E e) | 先 lock.lock() ,插尾巴,满了就失败,失败直接返回 |
public boolean offer(E e, long timeout, TimeUnit unit) | 先 lock.lock() ,插尾巴,满了就失败,失败循环尝试 N毫秒,依旧不行就返回失败 |
public void put(E e) throws InterruptedException | 先 lock.lock() ,插尾巴,满了就失败,失败就直接无限循环(可以打断) |
public E peek() | 先 lock.lock() ,从头部拉一个元素出来,没元素返回 nul |
public E poll() | 先 lock.lock() ,从头部拉一个元素出来,没元素返回 null |
public E poll(long timeout, TimeUnit unit) throws InterruptedException | 先 lock.lock() ,从头部拉一个元素出来,没元素尝试一段时间,最后还是不行就返回 null |
public E take() throws InterruptedException | 先 lock.lock() ,空了就失败,,失败就直接无限循环(可以打断) |
public int size() | 先 lock.lock() ,再取size |
public int remainingCapacity() | 先 lock.lock() ,再查剩余容量 |
public boolean remove(Object o) | 先 lock.lock() ,然后从头删掉一个 |
public boolean contains(Object o) | 先 lock.lock() ,再去看内部是否contains |
public Object[] toArray() | 先 lock.lock() ,再底层调用 System.arraycopy 进行数组的复制 |
public T[] toArray(T[] a) | 先 lock.lock() ,复制Queue中元素到数组里面去 |
public void clear() | 先 lock.lock() ,把数组内元素设置为 null |
public int drainTo(Collection super E> c) | 把this的元素搬运到另一个Collection c |
public int drainTo(Collection super E> c, int maxElements) | 把this的元素搬运到另一个Collection c |
public Iterator iterator() | 获取迭代器 Iterator |
private void readObject(java.io.ObjectInputStream s) | 从输入流读取数据 |
没有 writeObject方法 | 因为上面的变量都不是 transient ,也就是不用特殊处理 |
private static void test04() throws Exception{
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(2);
new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "--- put A");
arrayBlockingQueue.put("A");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
System.out.println("first take ...");
System.out.println(arrayBlockingQueue.take());
System.out.println("end take ...");
}
first take ...
Thread-0--- put A
A
end take ...
=== 点击查看top目录 ===
=== 点击查看top目录 ===
public class SynchronousQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -3223113410248163686L;
=== 点击查看top目录 ===
变量 | 备注 |
---|---|
static final int NCPUS = Runtime.getRuntime().availableProcessors(); | 可用CPU数量 |
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32; | 如果可用cpu是1 ,spin 0次 |
static final int maxUntimedSpins = maxTimedSpins * 16; | |
static final long spinForTimeoutThreshold = 1000L; | spin 上限阈值1000 |
private transient volatile Transferer transferer; | 内部类 Transferer |
private ReentrantLock qlock; | 对象序列化的时候用到 |
private WaitQueue waitingProducers; | 对象序列化的时候用到 |
private WaitQueue waitingConsumers; | 对象序列化的时候用到 |
=== 点击查看top目录 ===
方法 | 备注 |
---|---|
public void put(E e) throws InterruptedException | 不会 timeout ,一直阻塞尝试塞入 |
public boolean offer(E e, long timeout, TimeUnit unit) | 会 timeout,尝试塞入 ,失败返回 false |
public boolean offer(E e) | 会 timeout,尝试塞入 ,一次失败返回 false |
public E take() throws InterruptedException | 不会 timeout ,一直阻塞 |
public E poll(long timeout, TimeUnit unit) throws InterruptedException | 会 timeout ,取不到就返回false |
public E poll() | 会 timeout ,一次取不到就返回false |
=== 点击查看top目录 ===
方法 | 备注 |
---|---|
public boolean isEmpty() | return true; |
public int size() | return 0; |
public int remainingCapacity() | return 0; |
public void clear() | {} |
public boolean contains(Object o) | return false; |
public boolean remove(Object o) | return false; |
public boolean containsAll(Collection> c) | return c.isEmpty(); c是empty就返回true,其余都是false |
public boolean removeAll(Collection> c) | return false; |
public boolean retainAll(Collection> c) | return false; |
public E peek() | return null; 窥探一下都不给 |
public Iterator iterator() | return Collections.emptyIterator(); |
public Spliterator spliterator() | return Spliterators.emptySpliterator(); |
public Object[] toArray() | return new Object[0]; |
public T[] toArray(T[] a) | if (a.length > 0) a[0] = null; return a; |
public int drainTo(Collection super E> c) | 倒水,poll 最多一个 |
public int drainTo(Collection super E> c, int maxElements) | 倒水,poll 最多一个 |
private void writeObject(java.io.ObjectOutputStream s) | |
private void readObject(java.io.ObjectInputStream s) |
=== 点击查看top目录 ===
public static void main(String[] args) throws Exception{
// 如果为 true566,则等待线程以 FIFO 的顺序竞争访问;否则顺序是未指定的。
// SynchronousQueue sc = new SynchronousQueue<>(true);//fair
SynchronousQueue<Integer> sc = new SynchronousQueue<>(); // 默认不指定的话是false,不公平的
new Thread(() ->{ // 生产者线程
while (true){
try {
//将指定元素添加到此队列,如有必要则等待另一个线程接收它。
// sc.put(new Random().nextInt(50));
// 如果另一个线程正在等待以便接收指定元素,则将指定元素插入到此队列。如果没有等待接受数据的线程则直接返回false
// System.out.println("sc.offer(new Random().nextInt(50)): "+sc.offer(new Random().nextInt(50)));
//如果没有等待的线程,则等待指定的时间。在等待时间还没有接受数据的线程的话,直接返回false
int temp = new Random().nextInt(50);
Thread.sleep(500);
System.out.println("put : " + temp + ", 操作运行完毕..." ); //是操作完毕,并不是添加或获取元素成功!
sc.offer(temp,5,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
new Thread(() ->{ //消费者线程。
while (true){
try {
// 是操作完毕,并不是添加或者获取元素成功!
System.out.println("----------------> sc.take: " + sc.take() + ",获取操作运行完毕..");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
----------------> sc.take: 20,获取操作运行完毕..
put : 20, 操作运行完毕...
----------------> sc.take: 32,获取操作运行完毕..
put : 32, 操作运行完毕...
----------------> sc.take: 48,获取操作运行完毕..
put : 48, 操作运行完毕...
----------------> sc.take: 44,获取操作运行完毕..
put : 44, 操作运行完毕...
put : 18, 操作运行完毕...
----------------> sc.take: 18,获取操作运行完毕..
----------------> sc.take: 12,获取操作运行完毕..
put : 12, 操作运行完毕...
=== 点击查看top目录 ===
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
=== 点击查看top目录 ===
public static void main(String[] args) throws InterruptedException {
PriorityBlockingQueue<A> queue2 = new PriorityBlockingQueue<>();
queue2.put(new A());
}
private static class A{}
Exception in thread "main" java.lang.ClassCastException: indi.sword.util.concurrent._21_03_TestBlockingQueue_PriorityBlockingQueue$A cannot be cast to java.lang.Comparable
at java.util.concurrent.PriorityBlockingQueue.siftUpComparable(PriorityBlockingQueue.java:358)
at java.util.concurrent.PriorityBlockingQueue.offer(PriorityBlockingQueue.java:490)
at java.util.concurrent.PriorityBlockingQueue.put(PriorityBlockingQueue.java:512)
at indi.sword.util.concurrent._21_03_TestBlockingQueue_PriorityBlockingQueue.main(_21_03_TestBlockingQueue_PriorityBlockingQueue.java:28)
=== 点击查看top目录 ===
public static void main(String[] args) throws InterruptedException {
PriorityBlockingQueue<PriorityElement> queue = new PriorityBlockingQueue<>();
for (int i = 0; i < 5; i++) {
Random random = new Random();
PriorityElement element = new PriorityElement(random.nextInt(10));
queue.put(element);
}
while (!queue.isEmpty()){
System.out.println(queue.take());
}
}
@Data
@ToString
class PriorityElement implements Comparable<PriorityElement>{
private int priority;// 定义优先级
public PriorityElement(int priority){
// 初始化优先级
this.priority = priority;
}
@Override
public int compareTo(PriorityElement o) {
//按照优先级大小进行排序
return this.getPriority() >= o.getPriority() ? -1: 1;
}
}
PriorityElement(priority=5)
PriorityElement(priority=4)
PriorityElement(priority=2)
PriorityElement(priority=1)
PriorityElement(priority=0)
=== 点击查看top目录 ===
DelayQueue阻塞的是其内部元素,DelayQueue中的元素必须实现 java.util.concurrent.Delayed接口
getDelay()方法的返回值就是队列元素被释放前的保持时间,如果返回0或者一个负值,就意味着该元素已经到期需要被释放,此时DelayedQueue会通过其take()方法释放此对象。
从 Delayed 接口定义可以看到,它还继承了Comparable接口,这是因为DelayedQueue中的元素需要进行排序,一般情况,我们都是按元素过期时间的优先级进行排序。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
public class _21_01_TestBlockingQueue_DelayQueue {
public static void main(String[] args) throws InterruptedException {
DelayQueue<DelayedElement> queue = new DelayQueue<>();
DelayedElement ele = new DelayedElement("cache 3 seconds",3000);
queue.put(ele);
System.out.println("has put into queue ...");
System.out.println(queue.take());
}
}
@ToString
class DelayedElement implements Delayed{
private long expired;
private long delay;
private String name;
public DelayedElement(String elementName,long delay){
this.name = elementName;
this.delay = delay;
expired = (delay + System.currentTimeMillis());
}
@Override
public int compareTo(Delayed o) {
DelayedElement cached = (DelayedElement)o;
return cached.getExpired() > this.getExpired() ? 1: -1;
}
@Override
public long getDelay(TimeUnit unit) {
return ( this.getExpired() - System.currentTimeMillis());
}
public long getExpired() {
return expired;
}
}
has put into queue ...
DelayedElement(expired=1571302866874, delay=3000, name=cache 3 seconds)
=== 点击查看top目录 ===
public class _21_02_TestBlockingQueue_DelayQueue {
static final int STUDENT_SIZE = 30;
public static void main(String[] args) throws InterruptedException {
Random r = new Random();
// 所有学生看做一个延迟队列
DelayQueue<Student> students = new DelayQueue<>();
// 构造一个线程池用来让学生们“做作业”
ExecutorService executorService = Executors.newFixedThreadPool(STUDENT_SIZE);
for (int i = 0; i < STUDENT_SIZE; i++) {
students.put(new Student("学生" + (i+1),3000 + r.nextInt(10000)));
}
// 开始做题
while(! students.isEmpty()){
executorService.execute(students.take());
}
executorService.shutdown();
}
}
class Student implements Runnable,Delayed{
private String name; // 学生姓名
private long costTime;// 该学生做题的时间,假设根据能力,我们事先推算出来的
private long finishedTime; // 完成时间 costTime + System.currentTimeMills()
public Student(String name,long costTime){
this.name = name;
this.costTime = costTime;
finishedTime = costTime + System.currentTimeMillis();
}
@Override
public void run() {
System.out.println(name + "交卷,用时: " + costTime / 1000 + "s");
}
@Override
public long getDelay(TimeUnit unit) {
return (finishedTime - System.currentTimeMillis());
}
@Override
public int compareTo(Delayed o) {
Student other = (Student)o;
return costTime >= other.costTime ? 1: -1;
}
}
学生17交卷,用时: 3s
学生2交卷,用时: 3s
学生3交卷,用时: 3s
学生16交卷,用时: 3s
学生30交卷,用时: 3s
学生22交卷,用时: 5s
学生29交卷,用时: 5s
学生25交卷,用时: 5s
学生18交卷,用时: 6s
学生28交卷,用时: 6s
学生14交卷,用时: 6s
学生23交卷,用时: 6s
学生19交卷,用时: 7s
学生11交卷,用时: 7s
学生9交卷,用时: 7s
学生27交卷,用时: 7s
学生7交卷,用时: 7s
学生15交卷,用时: 7s
学生26交卷,用时: 7s
学生8交卷,用时: 8s
学生4交卷,用时: 9s
学生1交卷,用时: 9s
学生24交卷,用时: 10s
学生20交卷,用时: 10s
学生6交卷,用时: 10s
学生10交卷,用时: 11s
学生21交卷,用时: 11s
学生12交卷,用时: 12s
学生13交卷,用时: 12s
学生5交卷,用时: 12s
=== 点击查看top目录 ===
下一章节::【Java Collection】子类 SynchronousQueue 图解剖析(五)
上一章节:【Java Collection】常见 List 子类剖析(三)