java-集合的补充

常见基础集合汇总

数据结构:栈

数据结构分为:

(1)逻辑结构 :--》思想上的结构--》卧室,厨房,卫生间 ---》线性表(数组,链表),图,树,栈,队列

(2)物理结构 :--》真实结构--》钢筋混凝土+牛顿力学------》紧密结构(顺序结构),跳转结构(链式结构) 

栈:

特点:后进先出(LIFO - last in first out):

实际应用:

(1)内存分析:形参,局部变量放入栈中。放入的那个区域的数据结构就是按照栈来做的。

(堆:利用完全二叉树的结构来维护一组数据)

1.package com.msb.test01;
2.
3.import java.util.Stack;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class Test {
9.    //这是main方法,程序的入口
10.    public static void main(String[] args) {
11.        /*
12.        Stack是Vector的子类,Vector里面两个重要的属性:
13.        Object[] elementData;底层依然是一个数组
14.        int elementCount;数组中的容量
15.         */
16.        Stack s = new Stack();
17.        s.add("A");
18.        s.add("B");
19.        s.add("C");
20.        s.add("D");
21.        System.out.println(s);//[A, B, C, D]
22.        System.out.println("栈是否为空:" + s.empty());
23.
24.        System.out.println("查看栈顶的数据,但是不移除:" + s.peek());
25.        System.out.println(s);
26.
27.        System.out.println("查看栈顶的数据,并且不移除:" + s.pop());
28.        System.out.println(s);
29.
30.        s.push("D");//和add方法执行的功能一样,就是返回值不同
31.        System.out.println(s);
32.
33.    }
34.}

(2)网络浏览器多会将用户最近访问过的网址组织为一个栈。这样,用户每访问一个新页面,其地址就会被存放至栈顶;而用户每按下一次“后退”按钮,即可沿相反的次序访问此前刚访问过的页面。

(3)主流的文本编辑器也大都支持编辑操作的历史记录功能(ctrl + z:撤销,ctrl + y:恢复),用户的编辑操作被依次记录在一个栈中。一旦出现误操作,用户只需按下“撤销”按钮,即可取消最近一次操作并回到此前的编辑状态。

Stack

1.package com.msb.test01;
2.
3.import java.util.Stack;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class Test {
9.    //这是main方法,程序的入口
10.    public static void main(String[] args) {
11.        /*
12.        Stack是Vector的子类,Vector里面两个重要的属性:
13.        Object[] elementData;底层依然是一个数组
14.        int elementCount;数组中的容量
15.         */
16.        Stack s = new Stack();
17.        s.add("A");
18.        s.add("B");
19.        s.add("C");
20.        s.add("D");
21.        System.out.println(s);//[A, B, C, D]
22.        System.out.println("栈是否为空:" + s.empty());
23.
24.        System.out.println("查看栈顶的数据,但是不移除:" + s.peek());
25.        System.out.println(s);
26.
27.        System.out.println("查看栈顶的数据,并且不移除:" + s.pop());
28.        System.out.println(s);
29.
30.        s.push("D");//和add方法执行的功能一样,就是返回值不同
31.        System.out.println(s);
32.
33.    }
34.}

同步类容器

比如ArrayList,HashMap,线程不安全,现在想把线程不安全的集合转换为线程安全的集合:

1.public class Test01 {
2.    //这是main方法,程序的入口
3.    public static void main(String[] args) {
4.        //ArrayList为案例:从线程不安全  转为线程安全:
5.        List list = Collections.synchronizedList(new ArrayList());
6.    }
7.}

试试ArrayList的线程不安全:

1.package com.msb.test02;
2.
3.import java.util.ArrayList;
4.import java.util.concurrent.ExecutorService;
5.import java.util.concurrent.Executors;
6.
7./**
8. * @author : msb-zhaoss
9. */
10.public class Demo {
11.    //这是main方法,程序的入口
12.    public static void main(String[] args) {
13.        //创建一个ArrayList集合:
14.        ArrayList list = new ArrayList();
15.
16.        //创建一个线程池:线程池定长100
17.        ExecutorService es = Executors.newFixedThreadPool(100);
18.
19.        //并发向集合中添加10000个数据:
20.        for (int i = 0; i < 10000; i++) {
21.            //每个线程处理任务:run方法中的内容就是线程单元,任务,实际线程执行的部分
22.            es.execute(new Runnable() {
23.                @Override
24.                public void run() {
25.                    list.add("aaa");
26.                }
27.            });
28.        }
29.
30.        //关闭线程池:
31.        es.shutdown();
32.
33.        //监控线程是否执行完毕:
34.        while(true){
35.            //线程都执行完以后返回true
36.            if(es.isTerminated()){
37.                System.out.println("所有的子线程都执行完毕了!");
38.                //执行完毕以后看一下集合中元素的数量:
39.                System.out.println(list.size());
40.                if(list.size() == 10000){
41.                    System.out.println("线程安全!");
42.                }else{
43.                    System.out.println("线程不安全!");
44.                }
45.
46.                //线程执行完以后,while循环可以停止:
47.                break;
48.            }
49.        }
50.    }
51.}

结果:

利用同步类容器解决:

1.package com.msb.test02;
2.
3.import java.util.ArrayList;
4.import java.util.Collections;
5.import java.util.List;
6.import java.util.concurrent.ExecutorService;
7.import java.util.concurrent.Executors;
8.
9./**
10. * @author : msb-zhaoss
11. */
12.public class Demo {
13.    //这是main方法,程序的入口
14.    public static void main(String[] args) {
15.        //创建一个ArrayList集合:
16.        ArrayList oldlist = new ArrayList();
17.        List list = Collections.synchronizedList(oldlist);
18.
19.        //创建一个线程池:线程池定长100
20.        ExecutorService es = Executors.newFixedThreadPool(100);
21.
22.        //并发向集合中添加10000个数据:
23.        for (int i = 0; i < 10000; i++) {
24.            //每个线程处理任务:run方法中的内容就是线程单元,任务,实际线程执行的部分
25.            es.execute(new Runnable() {
26.                @Override
27.                public void run() {
28.                    list.add("aaa");
29.                }
30.            });
31.        }
32.
33.        //关闭线程池:
34.        es.shutdown();
35.
36.        //监控线程是否执行完毕:
37.        while(true){
38.            //线程都执行完以后返回true
39.            if(es.isTerminated()){
40.                System.out.println("所有的子线程都执行完毕了!");
41.                //执行完毕以后看一下集合中元素的数量:
42.                System.out.println(list.size());
43.                if(list.size() == 10000){
44.                    System.out.println("线程安全!");
45.                }else{
46.                    System.out.println("线程不安全!");
47.                }
48.
49.                //线程执行完以后,while循环可以停止:
50.                break;
51.            }
52.        }
53.    }
54.}

结果:

源码解析:

ConcurrentMap并发容器

JDK5.0之后提供了多种并发类容器可以替代同步类容器,提升性能、吞吐量

ConcurrentHashMap替代HashMap、HashTable

ConcurrentSkipListMap替代TreeMap 

简单原理:

并发情况下,验证提高性能:

ConcunrrentHashMap :

1.public class Test {
2.    //这是main方法,程序的入口
3.    public static void main(String[] args) {
4.        //选择一个容器:
5.        ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
6.        
7.        //创建10个线程:
8.        for (int i = 0; i < 10; i++) {
9.            new Thread(new Runnable() {
10.                @Override
11.                public void run() {
12.                    long startTime = System.currentTimeMillis();
13.                    for (int j = 0; j < 1000000; j++) {
14.                        map.put("test" + j , j);
15.                    }
16.                    long endTime = System.currentTimeMillis();
17.                    System.out.println("一共需要的时间:" + (endTime - startTime));
18.                }
19.            }).start();
20.        }
21.    }
22.}

结果:

Hashtable:

1.package com.msb.test03;
2.
3.import java.util.Hashtable;
4.import java.util.concurrent.ConcurrentHashMap;
5.
6./**
7. * @author : msb-zhaoss
8. */
9.public class Test {
10.    //这是main方法,程序的入口
11.    public static void main(String[] args) {
12.        //选择一个容器:
13.        //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
14.        Hashtable map = new Hashtable();
15.        //创建10个线程:
16.        for (int i = 0; i < 10; i++) {
17.            new Thread(new Runnable() {
18.                @Override
19.                public void run() {
20.                    long startTime = System.currentTimeMillis();
21.                    for (int j = 0; j < 1000000; j++) {
22.                        map.put("test" + j , j);
23.                    }
24.                    long endTime = System.currentTimeMillis();
25.                    System.out.println("一共需要的时间:" + (endTime - startTime));
26.                }
27.            }).start();
28.        }
29.    }
30.}

结果:

HashMap:

1.package com.msb.test03;
2.
3.import java.util.HashMap;
4.import java.util.Hashtable;
5.import java.util.concurrent.ConcurrentHashMap;
6.
7./**
8. * @author : msb-zhaoss
9. */
10.public class Test {
11.    //这是main方法,程序的入口
12.    public static void main(String[] args) {
13.        //选择一个容器:
14.        //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
15.        //Hashtable map = new Hashtable();
16.        HashMap map = new HashMap();
17.        //创建10个线程:
18.        for (int i = 0; i < 10; i++) {
19.            new Thread(new Runnable() {
20.                @Override
21.                public void run() {
22.                    long startTime = System.currentTimeMillis();
23.                    for (int j = 0; j < 1000000; j++) {
24.                        map.put("test" + j , j);
25.                    }
26.                    long endTime = System.currentTimeMillis();
27.                    System.out.println("一共需要的时间:" + (endTime - startTime));
28.                }
29.            }).start();
30.        }
31.    }
32.}

线程安全的HashMap:

1.package com.msb.test03;
2.
3.import java.util.Collections;
4.import java.util.HashMap;
5.import java.util.Hashtable;
6.import java.util.Map;
7.import java.util.concurrent.ConcurrentHashMap;
8.
9./**
10. * @author : msb-zhaoss
11. */
12.public class Test {
13.    //这是main方法,程序的入口
14.    public static void main(String[] args) {
15.        //选择一个容器:
16.        //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
17.        //Hashtable map = new Hashtable();
18.        HashMap oldmap = new HashMap();
19.        Map map = Collections.synchronizedMap(oldmap);
20.        //创建10个线程:
21.        for (int i = 0; i < 10; i++) {
22.            new Thread(new Runnable() {
23.                @Override
24.                public void run() {
25.                    long startTime = System.currentTimeMillis();
26.                    for (int j = 0; j < 1000000; j++) {
27.                        map.put("test" + j , j);
28.                    }
29.                    long endTime = System.currentTimeMillis();
30.                    System.out.println("一共需要的时间:" + (endTime - startTime));
31.                }
32.            }).start();
33.        }
34.    }
35.}
36.

结果:

总结:

ConcurrentHashMap:性能高,线程安全

Hashtable: 线程安全,性能低

HashMap:线程不安全,性能高

线程安全的HashMap:线程安全,性能低

COW并发容器

【1】COW类并发容器,全称:Copy  On  Write容器,写时复制容器。(读写分离容器)     

【2】原理:

向容器中添加元素时,先将容器进行Copy复制出一个新容器,然后将元素添加到新容器中,再将原容器的引用指向新容器。

并发读的时候不需要锁定容器,因为原容器没有变化,所以可以读取原容器中的值,使用的是一种读写分离的思想。

3】这种设计的好处是什么呢?

注意上面的操作arr数组本身是无锁的,没有锁,在添加数据的时候,做了额外的复制,

此时如果有线程来读数据,那么读取的是老arr的数据,此时arr的地址还没有改呢,在我添加元素的过程中,

无论有多少个线程来读数据,都是读的原来的arr,不是新的arr

所以性能很高,读写分离。提高了并发的性能。如果再读再复制...

【4】注意:

CopyOnWrite容器只能保证数据的最终一致性,不能保证数据实时一致性。

所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

【5】适合特定场合:

这个应用场景显而易见,适合读多写少的情况。如果一万个线程都添加操作,都在集合中添加数据,那数组不断复制,长度不断+1,

那JVM肯定一直往上飙升,你用的时候肯定要评估使用场景的。

由于每次更新都会复制新容器,所以如果数据量较大并且更新操作频繁则对内存消耗很高,建议在高并发读的场景下使用。

【6】主要讲解:

COW容器有两种一种是CopyonWriteArrayList,一种是CopyOnWriteArraySet

一个是替代ArrayList,一个是代替Set  

CopyOnWriteArrayList

【1】代码:

1.package com.msb.test04;
2.
3.import java.util.concurrent.CopyOnWriteArrayList;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class Test {
9.    //这是main方法,程序的入口
10.    public static void main(String[] args) {
11.        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
12.        //添加方法:
13.        list.add(1);
14.        list.add(2);
15.        list.add(3);
16.        list.add(4);
17.        System.out.println(list);//[1, 2, 3, 4]
18.        list.add(3);//add方法无论元素是否存在,都可以添加进去--》添加重复的元素
19.        System.out.println(list);//[1, 2, 3, 4, 3]
20.        //adj. 缺席的;缺少的;心不在焉的;茫然的
21.        list.addIfAbsent(33);//添加不存在的元素--》不可以添加重复的数据
22.        System.out.println(list);//[1, 2, 3, 4, 3, 33]
23.    }
24.}
25.

【2】源码分析:

1.public class CopyOnWriteArrayList<E>{
2.        //底层基于数组实现的
3.        private transient volatile Object[] array;
4.        
5.        public CopyOnWriteArrayList() {
6.        setArray(new Object[0]);
7.    }
8.        
9.        final void setArray(Object[] a) {
10.        array = a; // array = new Object[0]
11.    }
12.        //add方法:
13.        public boolean add(E e) {
14.        final ReentrantLock lock = this.lock;
15.        lock.lock();
16.        try {
17.                        //返回底层array数组,给了elements
18.            Object[] elements = getArray();
19.                        //获取elements的长度---》获取老数组的长度
20.            int len = elements.length;
21.                        //完成数组的复制,将老数组中的元素复制到新数组中,并且新数组的长度加1操作
22.            Object[] newElements = Arrays.copyOf(elements, len + 1);
23.                        //将e元素放入新数组最后位置
24.            newElements[len] = e;
25.                        //array数组的指向从老数组变为新数组
26.            setArray(newElements);
27.            return true;
28.        } finally {
29.            lock.unlock();
30.        }
31.    }
32.        
33.        
34.        final Object[] getArray() {
35.        return array;//返回底层数组
36.    }
37.        
38.        
39.        private boolean addIfAbsent(E e, Object[] snapshot) {
40.        final ReentrantLock lock = this.lock;
41.        lock.lock();
42.        try {
43.                        //取出array数组给current
44.            Object[] current = getArray();
45.            int len = current.length;
46.            if (snapshot != current) {
47.                // Optimize for lost race to another addXXX operation
48.                int common = Math.min(snapshot.length, len);
49.                                //遍历老数组:
50.                for (int i = 0; i < common; i++)
51.                                        //eq(e, current[i])将放入的元素和老数组的每一个元素进行比较,如果有重复的元素,就返回false,不添加了
52.                    if (current[i] != snapshot[i] && eq(e, current[i]))
53.                        return false;
54.                if (indexOf(e, current, common, len) >= 0)
55.                        return false;
56.            }
57.                        //完成数组的复制,将老数组中的元素复制到新数组中,并且新数组的长度加1操作
58.            Object[] newElements = Arrays.copyOf(current, len + 1);
59.                        //将e元素放入新数组最后位置
60.            newElements[len] = e;
61.                        //array数组的指向从老数组变为新数组
62.            setArray(newElements);
63.            return true;
64.        } finally {
65.            lock.unlock();
66.        }
67.    }
68.        
69.        
70.}

CopyOnWriteArraySet

【1】代码:

1./**
2. * @author : msb-zhaoss
3. */
4.public class Test02 {
5.    //这是main方法,程序的入口
6.    public static void main(String[] args) {
7.        //创建一个集合:
8.        CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
9.        //在这里也体现出Set和List的本质区别,就在于是否重复
10.        //所以add方法直接不可以添加重复数据进去
11.        set.add(1);
12.        set.add(2);
13.        set.add(2);
14.        set.add(7);
15.        System.out.println(set);//[1, 2, 7]
16.        
17.    }
18.}

【2】源码:

1.public class CopyOnWriteArraySet<E>{
2.        //CopyOnWriteArraySet底层基于CopyOnWriteArrayList
3.        private final CopyOnWriteArrayList<E> al;
4.        
5.        public CopyOnWriteArraySet() {
6.        al = new CopyOnWriteArrayList<E>();
7.    }
8.        
9.        //添加方法:
10.        public boolean add(E e) {
11.        return al.addIfAbsent(e);//底层调用的还是CopyOnWriteArrayList的addIfAbsent
12.    }
13.}

总结:

由上面的源码看出,每次调用CopyOnWriteArraySet的add方法时候,其实底层是基于CopyOnWriteArrayList的addIfAbsent,

每次在addIfAbsent方法中每次都要对数组进行遍历,所以CopyOnWriteArraySet的性能低于CopyOnWriteArrayList

数据结构:队列

数据结构分为:

(1)逻辑结构 :--》思想上的结构--》卧室,厨房,卫生间 ---》线性表(数组,链表),图,树,栈,队列

(2)物理结构 :--》真实结构--》钢筋混凝土+牛顿力学------》紧密结构(顺序结构),跳转结构(链式结构) 

  队列:特点:先进先出 (FIFO)(first in first out)

他有两端,一端是让新元素进去,一端是让老元素出去

在需要公平且经济地对各种自然或社会资源做管理或分配的场合,无论是调度银行和医院的服务窗口,还是管理轮耕的田地和轮伐的森林,队列都可大显身手。 

甚至计算机及其网络自身内部的各种计算资源,无论是多进程共享的 CPU 时间,还是多用户共享的打印机,也都需要借助队列结构实现合理和优化的分配。

双端队列:两端都可以进行进队,出队的队列:

(1)前端,后端都可以进出:

(2)进行限制:

(3)特殊情况,双端队列实现栈操作:

栈和队列的物理结构实现 可以用线性表的数组,链表都可以

队列Queue

阻塞队列

BlockingQueue介绍

总结:BlockingQueue继承Queue,Queue继承自Collection

所以Collection最基础的增删改查操作是有的,在这个基础上,多了Queue的特点,在这个基础上又多了阻塞的特点,最终形成了BlockingQueue

什么叫阻塞?

常用的API:

添加: 

put是阻塞的

查询:

take是阻塞的

删除:

BlockingQueue常见子类

ArrayBlockingQueue

源码中的注释的解释说明:

【1】添加元素:

1.package com.msb.test05;
2.
3.import java.util.concurrent.ArrayBlockingQueue;
4.import java.util.concurrent.TimeUnit;
5.
6./**
7. * @author : msb-zhaoss
8. */
9.public class Test01 {
10.    //这是main方法,程序的入口
11.    public static void main(String[] args) throws InterruptedException {
12.        //创建一个队列,队列可以指定容量指定长度3:
13.        ArrayBlockingQueue aq = new ArrayBlockingQueue(3);
14.        //添加元素:
15.        //【1】添加null元素:不可以添加null元素,会报空指针异常:NullPointerException
16.        //aq.add(null);
17.        //aq.offer(null);
18.        //aq.put(null);
19.        //【2】正常添加元素:
20.        aq.add("aaa");
21.        aq.offer("bbb");
22.        aq.put("ccc");
23.        System.out.println(aq);//[aaa, bbb, ccc]
24.
25.        //【3】在队列满的情况下,再添加元素:
26.        //aq.add("ddd");//在队列满的情况下,添加元素 出现异常:Queue full
27.        //System.out.println(aq.offer("ddd"));//没有添加成功,返回false
28.        //设置最大阻塞时间,如果时间到了,队列还是满的,就不再阻塞了
29.        //aq.offer("ddd",2, TimeUnit.SECONDS);
30.        //真正阻塞的方法: put ,如果队列满,就永远阻塞 
31.        aq.put("ddd");
32.        System.out.println(aq);
33.    }
34.}

【2】获取元素:

1.package com.msb.test05;
2.
3.import javax.sound.midi.Soundbank;
4.import java.util.concurrent.ArrayBlockingQueue;
5.import java.util.concurrent.TimeUnit;
6.
7./**
8. * @author : msb-zhaoss
9. */
10.public class Test02 {
11.    //这是main方法,程序的入口
12.    public static void main(String[] args) throws InterruptedException {
13.        //创建一个队列,队列可以指定容量指定长度3:
14.        ArrayBlockingQueue aq = new ArrayBlockingQueue(3);
15.        aq.add("aaa");
16.        aq.add("bbb");
17.        aq.add("ccc");
18.        //得到头元素但是不移除
19.        System.out.println(aq.peek());
20.        System.out.println(aq);
21.        //得到头元素并且移除
22.        System.out.println(aq.poll());
23.        System.out.println(aq);
24.        //得到头元素并且移除
25.        System.out.println(aq.take());
26.        System.out.println(aq);
27.
28.        //清空元素:
29.        aq.clear();
30.        System.out.println(aq);
31.
32.        System.out.println(aq.peek());//null
33.        System.out.println(aq.poll());//null
34.        //设置阻塞事件,如果队列为空,返回null,时间到了以后就不阻塞了
35.        //System.out.println(aq.poll(2, TimeUnit.SECONDS));
36.        //真正阻塞:队列为空,永远阻塞
37.        System.out.println(aq.take());
38.
39.
40.    }
41.}

【3】源码:

1.public class ArrayBlockingQueue<E> {
2.        //底层就是一个数组:
3.        final Object[] items;
4.        //取元素用到的索引,初始结果为0
5.        int takeIndex;
6.        //放元素用到的索引,初始结果为0
7.        int putIndex;
8.        //数组中元素的个数:
9.        int count;
10.        
11.        //一把锁:这个锁肯定很多方法中用到了,所以定义为属性,初始化以后可以随时使用
12.    final ReentrantLock lock;
13.
14.    //锁伴随的一个等待吃:notEmpty
15.    private final Condition notEmpty;
16.
17.    //锁伴随的一个等待吃:notFull
18.    private final Condition notFull;
19.        
20.        //构造器:
21.        public ArrayBlockingQueue(int capacity) {//传入队列指定的容量
22.        this(capacity, false);
23.    }
24.        
25.        public ArrayBlockingQueue(int capacity, boolean fair) {//传入队列指定的容量
26.                //健壮性考虑
27.        if (capacity <= 0)
28.            throw new IllegalArgumentException();
29.                //初始化底层数组
30.        this.items = new Object[capacity];
31.                //初始化锁 和  等待队列
32.        lock = new ReentrantLock(fair);
33.        notEmpty = lock.newCondition();
34.        notFull =  lock.newCondition();
35.    }
36.        
37.        //两个基本方法:一个是入队,一个是出队  ,是其他方法的基础:
38.        //入队:
39.        private void enqueue(E x) {
40.        // assert lock.getHoldCount() == 1;
41.        // assert items[putIndex] == null;
42.        final Object[] items = this.items;//底层数组赋给items
43.                //在对应的下标位置放入元素
44.        items[putIndex] = x;
45.        if (++putIndex == items.length) //++putIndex putIndex 索引 加1 
46.            putIndex = 0;
47.                //每放入一个元素,count加1操作
48.        count++;
49.        notEmpty.signal();
50.    }
51.        
52.        
53.        //出队:
54.        private E dequeue() {
55.        // assert lock.getHoldCount() == 1;
56.        // assert items[takeIndex] != null;
57.        final Object[] items = this.items;//底层数组赋给items
58.        @SuppressWarnings("unchecked")
59.        E x = (E) items[takeIndex];//在对应的位置取出元素
60.        items[takeIndex] = null;//对应位置元素取出后就置为null
61.        if (++takeIndex == items.length)//++takeIndex 加1操作
62.            takeIndex = 0;
63.        count--;//每取出一个元素,count减1操作
64.        if (itrs != null)
65.            itrs.elementDequeued();
66.        notFull.signal();
67.        return x;//将取出的元素作为方法的返回值
68.    }
69.        
70.        
71.        
72.        
73.}

takeIndex和putIndex置为0的原因:

【4】其他的添加或者获取的方法都是依托与这个入队和出队的基础方法

【5】感受一下put和take的阻塞:

上面的while不可以换为if,因为如果notFull中的线程被激活的瞬间,有其他线程放入元素,那么队列就又满了

那么沿着await后面继续执行就不可以,所以一定要反复确定队列是否满的,才能放入元素

LinkedBlockingQueue

一个可选择的有边界的队列:意思就是队列的长度可以指定,也可以不指定

【1】添加元素:

1.package com.msb.test05;
2.
3.import java.util.concurrent.ArrayBlockingQueue;
4.import java.util.concurrent.LinkedBlockingQueue;
5.import java.util.concurrent.TimeUnit;
6.
7./**
8. * @author : msb-zhaoss
9. */
10.public class Test01 {
11.    //这是main方法,程序的入口
12.    public static void main(String[] args) throws InterruptedException {
13.        //创建一个队列,队列可以指定容量指定长度3:
14.        LinkedBlockingQueue aq = new LinkedBlockingQueue(3);
15.        //添加元素:
16.        //【1】添加null元素:不可以添加null元素,会报空指针异常:NullPointerException
17.        //aq.add(null);
18.        //aq.offer(null);
19.        aq.put(null);
20.        //【2】正常添加元素:
21.        aq.add("aaa");
22.        aq.offer("bbb");
23.        aq.put("ccc");
24.        System.out.println(aq);//[aaa, bbb, ccc]
25.
26.        //【3】在队列满的情况下,再添加元素:
27.        //aq.add("ddd");//在队列满的情况下,添加元素 出现异常:Queue full
28.        //System.out.println(aq.offer("ddd"));//没有添加成功,返回false
29.        //设置最大阻塞时间,如果时间到了,队列还是满的,就不再阻塞了
30.        //aq.offer("ddd",2, TimeUnit.SECONDS);
31.        //真正阻塞的方法: put ,如果队列满,就永远阻塞
32.        aq.put("ddd");
33.        System.out.println(aq);
34.    }
35.}
36.

【2】取出元素:

1.package com.msb.test05;
2.
3.import javax.sound.midi.Soundbank;
4.import java.util.concurrent.ArrayBlockingQueue;
5.import java.util.concurrent.LinkedBlockingQueue;
6.import java.util.concurrent.TimeUnit;
7.
8./**
9. * @author : msb-zhaoss
10. */
11.public class Test02 {
12.    //这是main方法,程序的入口
13.    public static void main(String[] args) throws InterruptedException {
14.        //创建一个队列,队列可以指定容量指定长度3:
15.        LinkedBlockingQueue aq = new LinkedBlockingQueue();
16.        aq.add("aaa");
17.        aq.add("bbb");
18.        aq.add("ccc");
19.        //得到头元素但是不移除
20.        System.out.println(aq.peek());
21.        System.out.println(aq);
22.        //得到头元素并且移除
23.        System.out.println(aq.poll());
24.        System.out.println(aq);
25.        //得到头元素并且移除
26.        System.out.println(aq.take());
27.        System.out.println(aq);
28.
29.        //清空元素:
30.        aq.clear();
31.        System.out.println(aq);
32.
33.        System.out.println(aq.peek());//null
34.        System.out.println(aq.poll());//null
35.        //设置阻塞事件,如果队列为空,返回null,时间到了以后就不阻塞了
36.        //System.out.println(aq.poll(2, TimeUnit.SECONDS));
37.        //真正阻塞:队列为空,永远阻塞
38.        System.out.println(aq.take());
39.
40.
41.    }
42.}
43.

【3】特点:

ArrayBlockingQueue : 不支持读写同时操作,底层基于数组的。

LinkedBlockingQueue:支持读写同时操作,并发情况下,效率高。底层基于链表。

【4】源码:

入队操作:

出队操作:

1.public class LinkedBlockingQueue<E>{
2.        //内部类Node就是链表的节点的对象对应的类:
3.        static class Node<E> {
4.        E item;//封装你要装的那个元素
5.        
6.        Node<E> next;//下一个Node节点的地址
7.
8.        Node(E x) { item = x; }//构造器
9.    }
10.        //链表的长度
11.        private final int capacity;
12.        //计数器:
13.        private final AtomicInteger count = new AtomicInteger();
14.        //链表的头结点
15.        transient Node<E> head;
16.        //链表的尾结点
17.        private transient Node<E> last;
18.        //取元素用的锁
19.        private final ReentrantLock takeLock = new ReentrantLock();
20.        //等待池
21.    private final Condition notEmpty = takeLock.newCondition();
22.    //放元素用的锁
23.    private final ReentrantLock putLock = new ReentrantLock();
24.    //等待池
25.    private final Condition notFull = putLock.newCondition();
26.        
27.        public LinkedBlockingQueue() {
28.        this(Integer.MAX_VALUE);//调用类本类的空构造器,传入正21亿
29.    }
30.        
31.        public LinkedBlockingQueue(int capacity) {
32.                //健壮性考虑
33.        if (capacity <= 0) throw new IllegalArgumentException();
34.                //给队列指定长度  
35.        this.capacity = capacity;
36.                //last,head指向一个新的节点,新的节点中 元素为null 
37.        last = head = new Node<E>(null);
38.    }
39.        
40.        
41.        //入队:
42.        private void enqueue(Node<E> node) {
43.        last = last.next = node;
44.    }
45.        
46.        //出队:
47.        private E dequeue() {
48.        Node<E> h = head;//h指向了head
49.        Node<E> first = h.next;//first 指向head的next
50.        h.next = h; // help GC   h.next指向自己,更容易被GC发现 被GC
51.        head = first;//head的指向指为first
52.        E x = first.item;//取出链中第一个元素,给了x
53.        first.item = null;
54.        return x;//把x作为方法的返回值
55.    }
56.}

【5】put的阻塞:

阻塞的前提是  队列是固定长度的 

SynchronousQueue

这个特殊的队列设计的意义:

测试1:先添加元素:

1.public class Test01 {
2.    //这是main方法,程序的入口
3.    public static void main(String[] args) {
4.        SynchronousQueue sq = new SynchronousQueue();
5.        sq.add("aaa");
6.    }
7.}

直接报错:说队列满了,因为队列没有容量,理解为满也是正常的

测试2:put方法  阻塞:队列是空的,可以理解为队列满了,满的话放入元素 put 一定会阻塞:

1.public class Test01 {
2.    //这是main方法,程序的入口
3.    public static void main(String[] args) throws InterruptedException {
4.        SynchronousQueue sq = new SynchronousQueue();
5.        sq.put("aaa");
6.    }
7.}

测试3:先取  再放:

1.package com.msb.test06;
2.
3.import java.util.concurrent.SynchronousQueue;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class Test02 {
9.    //这是main方法,程序的入口
10.    public static void main(String[] args) {
11.        SynchronousQueue sq = new SynchronousQueue();
12.
13.        //创建一个线程,取数据:
14.        new Thread(new Runnable() {
15.            @Override
16.            public void run() {
17.                while(true){
18.                    try {
19.                        System.out.println(sq.take());
20.                    } catch (InterruptedException e) {
21.                        e.printStackTrace();
22.                    }
23.                }
24.            }
25.        }).start();
26.
27.        //搞一个线程,往里面放数据:
28.        new Thread(new Runnable() {
29.            @Override
30.            public void run() {
31.                try {
32.                    sq.put("aaa");
33.                    sq.put("bbb");
34.                    sq.put("ccc");
35.                    sq.put("ddd");
36.                } catch (InterruptedException e) {
37.                    e.printStackTrace();
38.                }
39.
40.            }
41.        }).start();
42.    }
43.}

结果:

测试4:poll方法: 

1.package com.msb.test06;
2.
3.import java.util.concurrent.SynchronousQueue;
4.import java.util.concurrent.TimeUnit;
5.
6./**
7. * @author : msb-zhaoss
8. */
9.public class Test02 {
10.    //这是main方法,程序的入口
11.    public static void main(String[] args) {
12.        SynchronousQueue sq = new SynchronousQueue();
13.
14.        //创建一个线程,取数据:
15.        new Thread(new Runnable() {
16.            @Override
17.            public void run() {
18.                while(true){
19.                    try {
20.                        //设置一个阻塞事件:超出事件就不阻塞了
21.                        Object result = sq.poll(5, TimeUnit.SECONDS);
22.                        System.out.println(result);
23.                        if(result == null){
24.                            break;
25.                        }
26.                    } catch (InterruptedException e) {
27.                        e.printStackTrace();
28.                    }
29.                }
30.            }
31.        }).start();
32.
33.        //搞一个线程,往里面放数据:
34.        new Thread(new Runnable() {
35.            @Override
36.            public void run() {
37.                try {
38.                    sq.put("aaa");
39.                    sq.put("bbb");
40.                    sq.put("ccc");
41.                    sq.put("ddd");
42.                } catch (InterruptedException e) {
43.                    e.printStackTrace();
44.                }
45.
46.            }
47.        }).start();
48.    }
49.}

注意:取出元素 不能用peek,因为peek不会将元素从队列中拿走,只是查看的效果;

PriorityBlockingQueue

带有优先级的阻塞队列。

优先级队列,意味着队列有先后顺序的,数据有不同的权重。 

无界的队列,没有长度限制,但是在你不指定长度的时候,默认初始长度为11,也可以手动指定,

当然随着数据不断的加入,底层(底层是数组Object[])会自动扩容,直到内存全部消耗殆尽了,导致 OutOfMemoryError内存溢出 程序才会结束。

不可以放入null元素的,不允许放入不可比较的对象(导致抛出ClassCastException),对象必须实现内部比较器或者外部比较器。 

测试1:添加null数据:

1.public class Test {
2.    //这是main方法,程序的入口
3.    public static void main(String[] args) {
4.        PriorityBlockingQueue pq = new PriorityBlockingQueue();
5.        pq.put(null);
6.    }
7.}

测试2:添加四个数据:

1.package com.msb.test07;
2.
3./**
4. * @author : msb-zhaoss
5. */
6.public class Student implements Comparable<Student> {
7.    String name;
8.    int age;
9.
10.    public Student() {
11.    }
12.
13.    public Student(String name, int age) {
14.        this.name = name;
15.        this.age = age;
16.    }
17.
18.    @Override
19.    public String toString() {
20.        return "Student{" +
21.                "name='" + name + '\'' +
22.                ", age=" + age +
23.                '}';
24.    }
25.
26.    @Override
27.    public int compareTo(Student o) {
28.        return this.age - o.age;
29.    }
30.}
1.package com.msb.test07;
2.
3.import java.util.concurrent.PriorityBlockingQueue;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class Test02 {
9.    //这是main方法,程序的入口
10.    public static void main(String[] args) {
11.        PriorityBlockingQueue<Student> pq = new PriorityBlockingQueue<>();
12.        pq.put(new Student("nana",18));
13.        pq.put(new Student("lulu",11));
14.        pq.put(new Student("feifei",6));
15.        pq.put(new Student("mingming",21));
16.        System.out.println(pq);
17.    }
18.}

结果:

发现结果并没有按照优先级顺序排列

测试3:取出数据:

1.package com.msb.test07;
2.
3.import java.util.concurrent.PriorityBlockingQueue;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class Test02 {
9.    //这是main方法,程序的入口
10.    public static void main(String[] args) throws InterruptedException {
11.        PriorityBlockingQueue<Student> pq = new PriorityBlockingQueue<>();
12.        pq.put(new Student("nana",18));
13.        pq.put(new Student("lulu",11));
14.        pq.put(new Student("feifei",6));
15.        pq.put(new Student("mingming",21));
16.        System.out.println("------------------------------------------");
17.        System.out.println(pq.take());
18.        System.out.println(pq.take());
19.        System.out.println(pq.take());
20.        System.out.println(pq.take());
21.    }
22.}
23.

从结果证明,这个优先级队列,并不是在put数据的时候计算谁在前谁在后

而是取数据的时候,才真正判断谁在前 谁在后

优先级 --》取数据的优先级

DelayQueue

一、DelayQueue是什么

DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。

当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。

消费者线程查看队列头部的元素,注意是查看不是取出。然后调用元素的getDelay方法,如果此方法返回的值小0或者等于0,则消费者线程会从队列中取出此元素,并进行处理。如果getDelay方法返回的值大于0,则消费者线程wait返回的时间值后,再从队列头部取出元素,此时元素应该已经到期。

注意:不能将null元素放置到这种队列中。

二、DelayQueue能做什么

1. 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。 
2. 饿了吗订餐通知:下单成功后60s之后给用户发送短信通知。

3. 关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。

4. 缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。

5. 任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。  

案例:

1.package com.msb.test08;
2.
3.import java.util.concurrent.Delayed;
4.import java.util.concurrent.TimeUnit;
5.
6./**
7. * @author : msb-zhaoss
8. */
9.public class User implements Delayed {
10.    private int id;//用户id
11.    private String name;//用户名字
12.    private long endTime;//结束时间
13.
14.    public int getId() {
15.        return id;
16.    }
17.
18.    public void setId(int id) {
19.        this.id = id;
20.    }
21.
22.    public String getName() {
23.        return name;
24.    }
25.
26.    public void setName(String name) {
27.        this.name = name;
28.    }
29.
30.    public long getEndTime() {
31.        return endTime;
32.    }
33.
34.    public void setEndTime(long endTime) {
35.        this.endTime = endTime;
36.    }
37.
38.    public User(int id, String name, long endTime) {
39.        this.id = id;
40.        this.name = name;
41.        this.endTime = endTime;
42.    }
43.
44.    //只包装用户名字就可以
45.    @Override
46.    public String toString() {
47.        return "User{" +
48.                "name='" + name + '\'' +
49.                '}';
50.    }
51.
52.    @Override
53.    public long getDelay(TimeUnit unit) {
54.        //计算剩余时间 剩余时间小于0 <=0  证明已经到期
55.        return this.getEndTime() - System.currentTimeMillis();
56.    }
57.
58.    @Override
59.    public int compareTo(Delayed o) {
60.        //队列中数据 到期时间的比较
61.        User other = (User)o;
62.        return ((Long)(this.getEndTime())).compareTo((Long)(other.getEndTime()));
63.    }
64.}

compareTo:看谁先被移除 

getDelay :看剩余时间 

1.package com.msb.test08;
2.
3.import java.util.concurrent.DelayQueue;
4.
5./**
6. * @author : msb-zhaoss
7. */
8.public class TestDelayQueue {
9.    //创建一个队列:
10.    DelayQueue<User> dq = new DelayQueue<>();
11.
12.    //登录游戏:
13.    public void login(User user){
14.        dq.add(user);
15.        System.out.println("用户:[" + user.getId() +"],[" + user.getName() + "]已经登录,预计下机时间为:" + user.getEndTime() );
16.    }
17.
18.    //时间到,退出游戏,队列中移除:
19.    public void logout(){
20.        //打印队列中剩余的人:
21.        System.out.println(dq);
22.        try {
23.            User user = dq.take();
24.            System.out.println("用户:[" + user.getId() +"],[" + user.getName() + "]上机时间到,自动退出游戏");
25.        } catch (InterruptedException e) {
26.            e.printStackTrace();
27.        }
28.    }
29.
30.    //获取在线人数:
31.    public int onlineSize(){
32.        return dq.size();
33.    }
34.
35.    //这是main方法,程序的入口
36.    public static void main(String[] args) {
37.        //创建测试类对象:
38.        TestDelayQueue test = new TestDelayQueue();
39.
40.        //添加登录的用户:
41.        test.login(new User(1,"张三",System.currentTimeMillis()+5000));
42.        test.login(new User(2,"李四",System.currentTimeMillis()+2000));
43.        test.login(new User(3,"王五",System.currentTimeMillis()+10000));
44.        //一直监控
45.        while(true){
46.            //到期的话,就自动下线:
47.            test.logout();
48.            //队列中元素都被移除了的话,那么停止监控,停止程序即可
49.            if(test.onlineSize() == 0){
50.                break;
51.            }
52.        }
53.    }
54.}

双端队列Deque

1.package com.msb.test08;
2.
3.import java.util.Collection;
4.import java.util.Deque;
5.import java.util.Iterator;
6.import java.util.LinkedList;
7.
8./**
9. * @author : msb-zhaoss
10. */
11.public class Test03 {
12.    //这是main方法,程序的入口
13.    public static void main(String[] args) {
14.        /*
15.        双端队列:
16.        Deque<E> extends Queue
17.        Queue一端放 一端取的基本方法  Deque是具备的
18.        在此基础上 又扩展了 一些 头尾操作(添加,删除,获取)的方法
19.         */
20.        Deque<String> d = new LinkedList<>() ;
21.        d.offer("A");
22.        d.offer("B");
23.        d.offer("C");
24.        System.out.println(d);//[A, B, C]
25.
26.        d.offerFirst("D");
27.        d.offerLast("E");
28.        System.out.println(d);//[D, A, B, C, E]
29.
30.        System.out.println(d.poll());
31.        System.out.println(d);//[A, B, C, E]
32.
33.        System.out.println(d.pollFirst());
34.        System.out.println(d.pollLast());
35.        System.out.println(d);
36.    }
37.}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值