一、简要关系结构图

JavaSE 集合_集合

二、ArrayList

1、jdk 1.7 原理

调用空构造器时初始化 Object[] elementData 数组,长度为10;

初始化 size 属性,值为0;

当elementData 数组中的10个位置都有元素后,添加第11个元素时数组扩容;

扩容长度为原数组的1.5倍;

扩容逻辑是创建一个长度为 15 的数组,将旧数组的数据拷贝到新数组中。

2、jdk 1.8 原理

调用空构造器时初始化 Object[] elementData 数组,长度为 0;

初始化 size 属性,值为0;

添加第一个元素时,进行数组扩容;创建一个长度为 10 的数组,将该元素添加到新数组中;

剩余扩容逻辑与 jdk 1.7 一致;

3、特点

查询快,增删慢;

三、LinkedList

1、原理

是一个双向链表;维护了头节点和尾节点。

维护了一个size,用于计数。

每一个节点对应的类是 linkedList的内部类Node<E>

每一个节点包含三个主要参数:上一个节点地址、下一个节点地址、当前节点存储的数据;

调用 get方法传入索引值,底层会判断当前索引是否大于size的一半,用来判断从头遍历还是从尾部遍历(for循环遍历); 

使用for循环和for-each循环实际上是使用 iterator 迭代器进行遍历;

2、特点

查询慢,增删快。

四、Vector

一个被淘汰的集合

底层数组扩容为原数组的 2 倍;

add方法被 synchronized 修饰,表示线程安全;

五、迭代器

1、面试题:iterator()、Iterator、Iterable的关系?

-- Iterable<E>是一个接口,Collection 接口继承 Iterable<E>接口;

-- iterator() 是 Iterable<E>接口 中的一个抽象方法,在具体实现类中被实现;

-- iterator() 方法的返回值类型 是 Iterator<E> 接口,这个接口有两个经典抽象方法:

1、hasNext();

2、next(); 

-- hasNext()和next()这两个方法在 ArrayList 的内部类 Itr中被实现;

-- hasNext() 方法具体实现是 判断计数器是否与当前size相同;

-- next() 方法具体实现是 根据下标返回数组中的元素;

2、ListIterator

是一个增强型的 iterator ,提供了更多对 List的操作;

TreeMap实现类

唯一,有序(按升序或者降序)

key对应的类型一定要实现比较器(内部比较器、外部比较器)

底层:二叉树,每个节点都是一个 Entry<K,V>对象

每个节点对应的类型:Entry<K,V>,这个类包含6个属性,key、value,parent等信息

六、HashMap

1、原理

分类

解释

构造器

-- key唯一,且可以为 null;value没特别要求;

-- 底层数据结构是 数组 + 链表;

-- 提供空参构造器和带参构造器,如果使用带参构造器指定数组长度,会默认将这个长度改成 2 ^ n;

-- 底层数据结构有个 Entry<K,V>类型的主数组,默认长度为16;

-- 且这个数组的长度只能是 2 的 n 次方;

-- 目的是使用"与运算"代替"取余运算"提高效率;

-- 同时减少"哈希冲突" ;

-- 在 put 方法中 ,对key的hashCode 和 数组长度做取余运算,来确定存放位置下标;

-- 有个加载因子的属性,默认是0.75;

-- 有个size属性,每put一个元素,size+1;

-- 有个threshold属性,用来表示当前map的扩容边界值;

put方法

-- 在put方法中,先对key求hashCode,再用这个hashCode 和 主数组 取余,来确定主数组的下标;

-- 如果在主数组中当前下标处没东西,就直接把元素放在这个位置;

-- JDK 1.7 头插法;

扩容

-- size >= 扩容临界值,且当前节点的数组下标位置已有数据,会触发扩容;

-- 扩容为原数组的2倍,将旧数组的数据存到新数组中;

-- 重新计算当前节点的下标;

面试题

1、为什么加载因子默认为0.75?

      -- 如果太大,会增加hash碰撞概率,导致链表太长,查询效率低;

      -- 如果太小,会导致主数组容易扩容,链表太短,浪费空间;

2、为什么主数组长度一定是 2 ^ n?

      -- 在put方法中通过key的hashCode计算数组下标时,使用与运算代替取余运算提高效率; 

      -- 降低下标位置冲突概率;

2、JDK 1.8 HashMap 原理 

分类

解释

底层数据结构

-- 数组 + 链表(单向链表) + 红黑树。

-- 数组的类型是 Node<K,V> 是HashMap的一个内部类。

树化

-- 链表的个数达到8个且主数组长度达到64时才会树化,否则只进行扩容操作。

初始化

-- 调用构造器创建HashMap对象只会声明一个主数组的类型。

-- 在put第一个元素的时候才会对这个主数组做初始化操作,长度为16。

七、TreeMap

1、原理

-- 底层是二叉树结构。

-- 如果没指定比较器就使用key的 hashCode 做排序,来确定新的节点放左边还是右边。 

-- 初始化:声明一个comparator(比较器)。

-- 底层维护了一个root节点,第一次put键值对就保存进root节点内。

-- 之后put键值对就使用比较器确定新节点放到左边还是右边。

八、HashTable

1、原理

-- 初始化:默认容量 = 11,加载因子 = 0.75f,创建一个 Entry<?,?>[] table;

-- 底层维护了一个table(数组),数组中每个元素都有next属性,结构和HashMap一样,保存键值对。

-- put方法:

        有返回值,如果key重复,返回上一次保存的value,新value覆盖旧value。

        如果key不重复,计算hashCode且取余后确定key的位置。

        被 synchronized 修饰,表示线程安全。

九、LinkedHashMap

1、原理

-- 是HashMap的子类。

-- 初始化:创建一个HashMap,声明输出顺序以插入顺序为准。

-- put方法:就是HashMap的put方法。

-- get方法:是自己的,取到节点后做后置处理。

2、比较

HashMap

HashTable

LinkedHashMap

JDK 1.2 诞生。

线程不安全,效率高。

允许key = null。

JDK 1.0 诞生。

线程安全,效率低。

不允许key = null。

-- 可以实现按输入顺序来输出;

十、TreeSet实现类

唯一,有序(按升序或者降序);

-- 借助 TreeMap 实现,每个节点的value都是一个Object对象 --> 只在key的位置保存数据;

-- 所以 TreeSer存储数据的特点就是 TreeMap的key的特点;

十一、HashSet实现类

-- 借助 HashMap实现,每个节点的value都是一个Object对象 --> 只在key的位置保存数据;

-- 所以 HashSet存储数据的特点就是 HashMap的key的特点;

十二、ConcurrentHashMap

1、原理

-- 初始化:只声明一个对象,不申请存储空间。

-- put方法:初次put,创建存储空间,长度 = 16,这个过程是线程安全的。

-- 在写数据过程中都是数据安全的,只锁住当前占用数组位置的数据,提高了执行效率。

2、比较

ConcurrentHashMap

HashTable

HashMap

--线程安全

--细粒度锁,高效

--锁在片区上,16个片区

--线程安全

--粗粒度锁,低效

--锁在主数组上

--线程不安全

--无锁,高效

十三、同步集合

public class Test01 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        test01(list1);
        System.out.println("=============================");
        List<String> list2 = new ArrayList<>();
        test02(list2);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

1、线程不安全示例

public static void test01(List<String> list) {
    //创建一个线程池
    ExecutorService executorService = Executors.newFixedThreadPool(100);
    //并发向集合中添加10000个数据
    for (int i = 0; i < 10000; i++) {
        //每个线程处理任务:run方法中的内容就是线程单元,每个线程执行的部分
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                list.add("aaa");
            }
        });
    }
    //关闭线程池资源
    executorService.shutdown();
    //监控线程是否执行完毕
    while (true){
        //线程都执行完返回true
        if (executorService.isTerminated()){
            System.out.println("执行完成");
            //执行完毕以后看一下集合中元素的个数
            System.out.println(list.size());
            break;
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

2、线程安全示例

public static void test02(List<String> oldlist) {
    List<String> list = Collections.synchronizedList(oldlist);
    //创建一个线程池
    ExecutorService executorService = Executors.newFixedThreadPool(100);
    //并发向集合中添加10000个数据
    for (int i = 0; i < 10000; i++) {
        //每个线程处理任务:run方法中的内容就是线程单元,每个线程执行的部分
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                list.add("aaa");
            }
        });
    }
    //关闭线程池资源
    executorService.shutdown();
    //监控线程是否执行完毕
    while (true){
        //线程都执行完返回true
        if (executorService.isTerminated()){
            System.out.println("执行完成");
            //执行完毕以后看一下集合中元素的个数
            System.out.println(list.size());
            break;
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

十四、COW并发容器

1、Copy On Write 写时复制容器

写数据时先复制一个容器,新容器容量 +1,在加出来的位置写入新数据,修改老容器的指向。

执行过程:

1、此时有一个数组 N

2、需要向数组 N 添加数据时,先复制数组 N,得到 M

3、给 M 数组的长度 + 1

4、将新元素放到 M 中 加出来的长度的位置

5、把 M 的地址指向给 N

2、CopyOnWriteArrayList

add()

addIfAbsent()  --> 使用这个方法不会添加重复数据,每次都要对数组进行遍历。

3、CopyOnWriteArraySet

底层容器是 CopyOnWriteArrayList

add() 方法就是上面的 addIfAbsent()

十五、Queue 队列

1、关系结构图

JavaSE 集合_集合_02

2、BlockingQueue 阻塞队列

Queue 继承自 Collection ,具有Collection的功能。

BlockingQueue继承自Queue,具有队列的特点。

具有阻塞的特点。

入队:

非阻塞队列:队列满时,放入第11个数据,数据丢失。

阻塞队列:队列满时,放入第11个数据,进入阻塞,等待队列中有空位。

出队:

非阻塞队列:队列空时,取数据得到 null。

阻塞队列:队列空时,取数据进入阻塞,等待队列中有数据。

3、ArrayBlockingQueue

基于数组实现的阻塞队列,具有阻塞队列的特点;

需要指定数组长度;

不能添加null,会报空指针异常;

因为是基于数组实现,所以不支持读写同时操作,效率略低;

底层插入元素和取出元素用的是同一把锁;

底层实现基于生产者消费者模型和线程通信;

① 基本方法对比

添加数据

取出数据

add() 非阻塞;

队列满时添加数据报错

peek() 非阻塞;

取头部数据;

不移除这个数据;

队列空时获取到 null;

offer() 不完全阻塞;

可以指定阻塞时间,阻塞时间到达时队列满返回 false

poll() 不完全阻塞;

取头部数据;

移除这个数据;

可以指定阻塞时间,时间到时队列空获取到 null;

put()完全阻塞;

队列满时阻塞,一直满一直阻塞

take() 完全阻塞;

取头部数据;

移除这个数据;

队列空时阻塞,一直空一直阻塞

② 源码解析
public class ArrayBlockingQueue<E> {

    // 底层就是一个数组
    final Object[] items;

    // 取元素索引,初始值为0
    int takeIndex;

    // 放元素索引,初始值为0
    int putIndex;

    // 计数器,数组中元素个数
    int count;

    // 一把锁,这个锁在很多地方用到,所以定义为属性;
    final ReentrantLock lock;

    // 锁伴随的等待池 notEmpty
    private final Condition notEmpty;

    // 锁伴随的等待池 notFull
    private final Condition notFull;

    // 构造器
    // 必须传入队列指定的容量
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    //传入指定的容量
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        //1、创建数组,指定长度 
        this.items = new Object[capacity]; 
        //2、初始化锁和等待队列
        lock = new ReentrantLock(fair); 
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

    // 两个基本方法,一个入队,一个出队
    // 入队方法
    private void enqueue(E x) {
        // 底层数组赋值给 items
        final Object[] items = this.items;
        // 在对应下标位置放入元素
        items[putIndex] = x;
        // putIndex索引 做+1操作
        if (++putIndex == items.length)
            // 底层数组放到最后一格时,putIndex索引归0
            // 为了给下一轮放元素做准备
            putIndex = 0;
        // 每放入一个元素,计数器+1操作
        count++;
        // 这一步很重要
        // 涉及到线程通信
        // 队列的底层数组放入一个元素后,会通知取数据的等待队列,将notEmpty队列的线程唤醒
        notEmpty.signal();
    }

    //出队方法
    private E dequeue() {
        // 底层数组赋值给 items
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        // 在对应下标位置取出元素,将元素作为返回值
        // 此时takeIndex = 0
        E x = (E) items[takeIndex];
        // 对应位置元素取出后置为null
        items[takeIndex] = null;
        // takeIndex索引 做+1操作
        if (++takeIndex == items.length)
            // 底层数组取到最后一格时,takeIndex索引归0
            // 为了给下一轮取元素做准备
            takeIndex = 0;
        // 每取出一个元素,计数器-1操作
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        // 这一步很重要
        // 涉及到线程通信
        // 队列的底层数组取出一个元素后,这时数组有空位了;
        // 会通知放数据的等待队列,将notFull队列的线程唤醒;
        notFull.signal();
        return x;
    }

    // put方法
    public void put(E e) throws InterruptedException {
        // 数据校验
        checkNotNull(e);
        // 获取锁,这个锁是唯一的,和take方法获取的锁是同一个
        final ReentrantLock lock = this.lock;
        // 上锁
        lock.lockInterruptibly();
        try {
            // 判断当前队列的元素个数是否达到底层数组的最大额度,
            // 如果队列已满就运行,放元素的线程进入等待队列
            while (count == items.length)
                // 进入等待队列 notFull
                notFull.await();
            // 入队操作
            enqueue(e);
        } finally {
            // 解锁
            lock.unlock();
        }
    }

    // take方法
    public E take() throws InterruptedException {
        // 获取锁,这个锁是唯一的,和put方法获取的锁是同一个
        final ReentrantLock lock = this.lock;
        // 上锁
        lock.lockInterruptibly();
        try {
            // 判断当前队列的元素个数是否为0
            // 如果当前队列没有元素了,取元素的线程进入等待队列;
            while (count == 0)
                // 进入阻塞队列 notEmpty
                notEmpty.await();
            // 出队操作
            return dequeue();
        } finally {
            // 解锁
            lock.unlock();
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.

4、面试题

问:put()和take()中的 while 为什么不用 if 替换?

答:

因为在放数据的线程被唤醒的瞬间,可能有其他线程先放入数据了,那么这时候底层数组是满的。

又因为线程在被唤醒后,会沿着await()后面的代码继续执行。

所以如果换成 if 会导致数据丢失。

5、LinkedBlockingQueue

基于链表实现,具有阻塞队列的特点。

可以不指定链表长度,不指定长度就是无限长。

支持读写同时操作,效率高。

底层插入元素和取出元素用的不是同一把锁。

以上ArrayBlockingQueue的方法,在LinkedBlockingQueue中均支持。

底层由其内部类Node<E> 实现。

Node 类有两个参数: E item,Node<E> next。

是一个单向链表。

① 源码解析
public class LinkedBlockingQueue<E>{
    // 底层是一个单向链表
    // 链表的节点就是这个内部类 Node
    // Node类包含两个属性,一个存放当前元素,另一个存放下一个节点的地址
    static class Node<E> {
        E item;
        Node<E> next;

        Node(E x) { item = x; }
    }

    // 指定的链表长度,也是队列容量
    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();

    // 空参构造器,创建无界链表
    // 但实际上创建的长度是Integer.MAX_VALUE
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    // 有参构造器
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity; // 设置最大容量
        last = head = new Node<E>(null); // 创建一个节点,这个节点既是头节点也是尾节点
    }

    // 入队方法
    private void enqueue(Node<E> node) {
        // 这里有两步操作
        // 1、尾节点的next指向新的node,这时链表节点多了一个
        // 2、last的指向后移一格,这时last还是链表的最后一个节点
        last = last.next = node;
    }

    // 出队方法
    private E dequeue() {
        // h指向 头节点
        Node<E> h = head;
        // h节点的下一个节点才是真正的第一个元素
        Node<E> first = h.next;
        // h的next指向自己
        // 这一步是断开 头节点 与后面链表的关联
        // 帮助垃圾回收器回收
        h.next = h; // help GC
        // head的指向后移一格,这时head还是链表的第一个节点
        head = first;
        // 获取‘头’元素
        E x = first.item;
        // 删除‘头’元素
        first.item = null;
        // 返回‘头’元素
        return x;
    }

    // put方法
    // 放入元素
    public void put(E e) throws InterruptedException {
        // 健壮性考虑
        if (e == null) throw new NullPointerException();
        // 创建一个 c = -1
        int c = -1;
        // 将放入的元素封装为一个Node对象
        Node<E> node = new Node<E>(e);
        // 拿到放入元素的锁
        final ReentrantLock putLock = this.putLock;
        // 拿到计数器
        final AtomicInteger count = this.count;
        // 上锁
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                notFull.await();
            }
            // 放数据操作
            enqueue(node);
            // 这一行有两个操作
            // 1、c = count
            // 2、count + 1 计数器+1
            c = count.getAndIncrement();
            // 这一步相当于判断 count < capacity
            // 如果count < capacity = true,那就意味着当前队列没满
            // 唤醒放元素的等待池中的线程
            if (c + 1 < capacity)
                // 在队列没满的时候,就可以唤醒放元素的等待池中的线程
                notFull.signal();
        } finally {
            // 解锁
            putLock.unlock();
        }
        // 这句判断相当于 count == 1
        // 如果队列中算上刚才放完的元素只有一个
        // 那么就通知取元素的线程
        if (c == 0)
            signalNotEmpty();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.

6、SynchronousQueue

特点:

这个队列必须先从队列中取出元素,然后才能向队列中添加元素。

这个队列连1个容量都没有。

优点:

方便、高效地进行线程间数据的传送。

效率极高,且不会产生队列中数据争抢问题。

① SynchronousQueue 工作图例

JavaSE 集合_集合_03

② 测试代码

一直蹲着等着取,有一个取一个。

JavaSE 集合_集合_04

7、PriorityBlockingQueue

是一个有顺序的阻塞队列,放元素的时候没体现顺序性,取元素的时候才会体现顺序性。

底层支持自动扩容,所以认为是无限大的队列。

队列中的元素必须实现比较器。

8、DelayQueue

是一个无界的阻塞队列。

用来存放实现了Delayed接口的对象。

① 案例代码
package allwe.thread08;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class User implements Delayed {
    private int id;
    private String name;
    private long endTime;

    public User() {
    }

    public User(int id, String name, long endTime) {
        this.id = id;
        this.name = name;
        this.endTime = endTime;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getEndTime() {
        return endTime;
    }

    public void setEndTime(long endTime) {
        this.endTime = endTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    // 看剩余时间
    @Override
    public long getDelay(TimeUnit unit) {
        // 剩余时间 《 0
        return this.getEndTime() - System.currentTimeMillis();
    }

    //看谁先被移除
    @Override
    public int compareTo(Delayed o) {
        // 到期时间比较
        User ohter = (User)o;
        return ((Long) this.getEndTime()).compareTo((Long) ohter.getEndTime());
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

package allwe.thread08;

import java.util.concurrent.DelayQueue;

public class Test {
    DelayQueue<User> a = new DelayQueue<>();

    public static void main(String[] args) {
        Test queue = new Test();
        //添加用户
        User user1 = new User(1,"张三",System.currentTimeMillis() + 5000);
        User user2 = new User(1,"李四",System.currentTimeMillis() + 10000);
        User user3 = new User(1,"拉拉",System.currentTimeMillis() + 2000);

        queue.login(user1);
        queue.login(user2);
        queue.login(user3);

        //一直监控
        while (true){
            //到期的话,就自动下线
            queue.logout();
            // 如果都下线了,就停止监控
            if (queue.onlineSize() == 0)
                break;
        }
    }

    public void login(User user){
        a.add(user);
        System.out.println("用户:[" + user.getId() + "],[" + user.getName() + "]已经登录,预计下机时间为:" + user.getEndTime());

    }

    public void logout(){
        System.out.println(a);
        try {
            User user = a.take();
            System.out.println("用户:[" + user.getId() + "],[" + user.getName() + "]已经下机");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //在线人数
    public int onlineSize(){
        return a.size();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.