1.并发容器类

  • 本章节主要是讲juc包下的并发容器,以及如何实现线程安全的源码。

ConcurrentSkipListMap

在这里插入图片描述

该结构是一个特殊的数据结构,是一种链表的结构目的就是为了能快速的查找。它的实现思路为添加类似索引的机制,索引包含节点。当索引数据量比较多的时候,又会建立索引分级。就是基于索引层次在创建一层索引。如上图。

  • 源码分析(put)
 */ // 这个方法目的就是查找到,这个新数据要插入到哪个节点的后面
    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) { // 当前level下存在合适的index
                    Node<K,V> n = r.node;
                    K k = n.key;
                    if (n.value == null) { // 如果node的value为空,这个节点就不需要了,
                        if (!q.unlink(r))// 当前level的链表中丢弃这个节点,将链表下下一个节点顶上来
                            break;           // restart 如果没有后续节点,则重试
                        r = q.right;         // reread r // 如果有,则继续寻找当前level中右边的index
                        continue;
                    }
                    if (cpr(cmp, key, k) > 0) { // 如果当前插入的key大于查找到的key,则继续往右查找(这个比较自己定义)
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if ((d = q.down) == null) // 找到最底层level的index
                    return q.node; // 如果本身就是最低level,则返回该节点
                q = d; // 如果还有低一个level的索引,则继续寻找
                r = d.right; // 那就继续寻找
            }
        }
    }
 private V doPut(K key, V value, boolean onlyIfAbsent) {
        Node<K,V> z;             // added node
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {// 查找这个节点应该塞到哪一个节点后面  cas+循环,无锁,不断重试
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                if (n != null) { // n 代表next位置,不为空则代表已经有数据存在,应该继续寻找后续的位置
                    Object v; int c;
                    Node<K,V> f = n.next;
                    if (n != b.next)               // inconsistent read 如果next发生了变化,表示出现脏读,则退出重试
                        break;
                    if ((v = n.value) == null) {   // n is deleted // 如果next节点的内容置空了,则应该删除该节点
                        n.helpDelete(b, f);
                        break;
                    }
                    if (b.value == null || v == n) // b is deleted // 如果当前node内容置空了,则退出重试
                        break;
                    if ((c = cpr(cmp, key, n.key)) > 0) { // 比较next和当前要插入的key,如果大于next的key,则继续寻找后续的next
                        b = n;
                        n = f;
                        continue;
                    }
                    if (c == 0) { // c 比对的结果,如果为0,则代表key相等,相等则直接cas替换
                        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)) // CAS 将当前数据塞到前置节点后面
                    break;         // restart if lost race to append to b // 如果失败,继续循环
                break outer;
            }
        }
        // 上面的代码,是将node插入到合适的位置。接下来就需要重构一下skiplist,判断是否需要增加level
        // Tony: 防止源码编译报错,我注释掉了后面一段
        int rnd = 0; // ThreadLocalRandom.nextSecondarySeed(); // 这里的代码是生成随机数
        if ((rnd & 0x80000001) == 0) { // test highest and lowest bits // 随机数是正偶数,则升级level
            int level = 1, max;
            while (((rnd >>>= 1) & 1) != 0) // 根据随机数计算level。从低2位开始向左有多少个连续的1
                ++level;
            Index<K,V> idx = null;
            HeadIndex<K,V> h = head;
            if (level <= (max = h.level)) {// 随机计算出来的level小于当前的level级别,不需要增加层级
                for (int i = 1; i <= level; ++i)
                    idx = new Index<K,V>(z, idx, null); // 构建一个index,归属于随机到的level
            }
            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]; // 创建一个index集合,数量是level数量
                for (int i = 1; i <= level; ++i) // 给上面新建的数组,赋值(idxs好像没什么卵用)
                    idxs[i] = idx = new Index<K,V>(z, idx, null);
                for (;;) {
                    h = head;
                    int oldLevel = h.level;
                    if (level <= oldLevel) // 新level小于就得level,代表其他线程更新了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); // 生成一个新的headIndex。最高level
                    if (casHead(h, newh)) { // 将head引用到新的index
                        h = newh;
                        idx = idxs[level = oldLevel];
                        break;
                    }
                }
            }
            // find insertion points and splice in         //将新的Index插入对应level链表上
            splice: for (int insertionLevel = level;;) {
                int j = h.level;
                for (Index<K,V> q = h, r = q.right, t = idx;;) {// 从head index开始找位置
                    if (q == null || t == null)
                        break splice;
                    if (r != null) { // 比对,找到level中合适的节点进行插入
                        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;
    }
  • 案例演示
public class ConcurrentSkipListMapDemo {
    public static void main(String[] args) {
        // 可以自己定义比较方式
        ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 自定义 key 的比较方式
                return 0;
            }
        });
        map.put("a", "1");
        map.put("c", "3");
        map.put("b", "2");
        System.out.println(map.keySet().toString());
    }
}
  • 执行结果
[a]

CopyOnWriteArrayList

  • 如果我们在遍历List的时候删除会有什么问题?
  • 案例演示
public class ArrayListDemo {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");

        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            // iterator.remove();// IllegalStateException
            arrayList.remove(iterator.next()); // ConcurrentModificationException
        }
    }
}
  • 执行结果
报错

在这里插入图片描述

首先存储方式是数组存储。在执行的时候,先讲Array复制一份出新的数据,之后在set进去。

  • 案例演示
public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");

        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            // iterator.remove();// IllegalStateException
            arrayList.remove(iterator.next()); // ConcurrentModificationException
        }
    }
}

额外的类

在这里插入图片描述

HashSet底层本质是使用HashMap进行存储,在增加元素的时候把元素作为map的key进行存储。最终将所有的key返回回来。

// 测试将对象作为key
public class ObjectHashMapDemo {

    public static void main(String[] args) {
        HashMap<User, String> map = new HashMap<>();
        User user = new User("tony");
        map.put(user, "test");
        System.out.println(map.get(user)); // 1、 输出什么  test
        user = new User("tony");
        System.out.println(map.get(user));  // 2、 输出什么 null
    }
}

class User {
    public String name;

    public User(String name) {
        this.name = name;
    }

//    如果不重写的话,输出结果第二个为null
//    @Override
//    public boolean equals(Object obj) {
//        return name.equals(((User)obj).name);
//    }
//
//    @Override
//    public int hashCode() {
//        return name.hashCode();
//    }
}

Queen

在这里插入图片描述

非阻塞的全部实现Queen接口的定义,阻塞的全部实现BlockingQueen

  • 案例演示
// 它是基于数组的阻塞循环队列, 此队列按 FIFO(先进先出)原则对元素进行排序。
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 构造时需要指定容量(量力而行),可以选择是否需要公平(最先进入阻塞的,先操作)
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3, false);
        // 1秒消费数据一个
        new Thread(() -> {
            while (true) {
                try {
                    System.out.println("取到数据:" + queue.poll()); // poll非阻塞
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
            }
        }).start();

        Thread.sleep(3000L); // 让前面的线程跑起来

        // 三个线程塞数据
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                     queue.put(Thread.currentThread().getName()); // put阻塞(如果当前的队列已经塞满了数据,线程不会继续往下执行,等待其他线程把
                    // 队列的数据拿出去// )
//                    queue.offer(Thread.currentThread().getName()); // offer非阻塞,满了返回false
                    System.out.println(Thread.currentThread() + "塞入完成");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

PriorityQueue

// 是一个带优先级的 队列,而不是先进先出队列。
// 元素按优先级顺序被移除,该队列也没有上限
// 没有容量限制的,自动扩容
// 虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),
// 但是如果队列为空,
// 那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,
// 入该队列中的元素要具有比较能力
public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 可以设置比对方式
        PriorityQueue<String> priorityQueue = new PriorityQueue<>(new Comparator<String>() {
            @Override //
            public int compare(String o1, String o2) {
                // 实际就是 元素之间的 比对。
                return 0;
            }
        });
        priorityQueue.add("c");
        priorityQueue.add("a");
        priorityQueue.add("b");

        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());

        PriorityQueue<MessageObject> MessageObjectQueue = new PriorityQueue<>(new Comparator<MessageObject>() {
            @Override
            public int compare(MessageObject o1, MessageObject o2) {
                return o1.order > o2.order ? -1 : 1;
            }
        });
    }
}

class MessageObject {
    String content;
    int order;
}

DelayQueue

// (基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,
// 只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。
// 如果延迟都还没有期满,则队列没有头部,并且poll将返回null。
// 当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,
// 则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
public class DelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Message> delayQueue = new DelayQueue<Message>();
        // 这条消息5秒后发送
        Message message = new Message("message - 00001", new Date(System.currentTimeMillis() + 5000L));
        delayQueue.add(message);

        while (true) {
            System.out.println(delayQueue.poll());
            Thread.sleep(1000L);
        }
        // 线程池中的定时调度就是这样实现的
    }
}

// 实现Delayed接口的元素才能存到DelayQueue
class Message implements Delayed {

    // 判断当前这个元素,是不是已经到了需要被拿出来的时间
    @Override
    public long getDelay(TimeUnit unit) {
        // 默认纳秒
        long duration = sendTime.getTime() - System.currentTimeMillis();
        return TimeUnit.NANOSECONDS.convert(duration, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return o.getDelay(TimeUnit.NANOSECONDS) > this.getDelay(TimeUnit.NANOSECONDS) ? 1 : -1;
    }

    String content;
    Date sendTime;

    /**
     * @param content  消息内容
     * @param sendTime 定时发送
     */
    public Message(String content, Date sendTime) {
        this.content = content;
        this.sendTime = sendTime;
    }

    @Override
    public String toString() {
        return "Message{" +
                "content='" + content + '\'' +
                ", sendTime=" + sendTime +
                '}';
    }
}

ConcurrentLinkedQueue

// 优势:无锁。
// 注意:批量操作不提供原子保证  addAll, removeAll, retainAll, containsAll, equals, and toArray
// 坑: size()方法每次都是遍历整个链表,最好不要频繁调用
// 如果没有阻塞要求,用这个挺好的(堆积数据)
public class ConcurrentLinkedQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 不需要指定容量,addAll
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();
        // 1秒消费数据一个
        new Thread(() -> {
            while (true) {
                try {
                    System.out.println("取到数据:" + queue.poll()); // poll非阻塞
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
            }
        }).start();

        Thread.sleep(3000L); // 让前面的线程跑起来

        // 三个线程塞数据
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    queue.offer(Thread.currentThread().getName()); // offer非阻塞,满了返回false
                    System.out.println(Thread.currentThread() + "塞入完成");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

SynchronousQueue

// 这是一个神奇的队列, 因为他不存数据。 手把手的交互数据
public class SynchronousQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
        // synchronousQueue.add("a"); // IllegalStateException
        // synchronousQueue.offer("a");
        System.out.println(synchronousQueue.poll()); // 非阻塞

        // 阻塞式的用法
        new Thread(() -> {
            try {
                System.out.println("等数据....");
                System.out.println(synchronousQueue.take());
                System.out.println("执行完毕....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(1000L);
        System.out.println("准备赛数据了");
        synchronousQueue.put("a");// 等待有人取走他
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值