4、并发容器

ConcurrentHashMap

Hash
散列,哈希:把任意长度的输入通过一种算法(散列),变换成固定长度的输出,这个输出值就是散列值;属于压缩映射,容易产生哈希冲突;

常见算法
直接取余,
md4、md5、sha 哈希算法,摘要

冲突解决办法

  1. 开放寻址
  2. 再散列
  3. 链地址法

HashMap线程为什么不安全
put操作会引起死循环,hashMap里的entry链表产生环形的数据结构,entry的next 永远不为null;

https://www.cnblogs.com/wfq9330/p/9023892.html 这是jdk1.8以前的说法
1.8以后是否安全值得思考

ConcurrentHashMap为什么线程安全?
都是分段锁
jdk1.7及以前
16个 Segment ,每个Segment 里单独有 table
每个table里有 一个 hashEntry
每个 Segment 都是 一个 ReentrantLock 的子类;
在这里插入图片描述

get
定位Segment : key的 hashCode 进行再散列 值的高位 、取模
定位 table :key的 hashCode 进行在散列取值 取模
依次扫描链表,要么找到元素,要么返回null
key的哈希值在散列算出属于哪个Segment ,然后在该table里 循环entry获取 key

put
初始化的时候只会初始化第一个 Segment
put的时候,会调用ensureSegment方法,判断当前key定位的Segment存在不存在,不存在就new
存在的话就找到所在的位,看key是否存在,存在就覆盖,不存在就追加在链表上
Segment 加锁,

扩容:
Segment不扩容,扩容的是 table 数组 * 2
table的数组 HashEntry下标index ( 4 -> 8), index 要么不变,要么 index + 4;

size 的时候进行两次不加锁的统计,一致直接返回结果,不一致则加锁统计
弱一致性,

jdk1.8及以后
在这里插入图片描述

与1.7相比的重大变化
取消了 Segment 数组,直接用 table 保存数据,锁的粒度更小,减少发生冲突的概率
链表+红黑树 ,纯链表的形式,时间复杂度 O(n),红黑树 :O(logn),性能提升很大;
链表转红黑树 : table下元素个数大于等于 8 ; 个数小于等于6 时 红黑树转为链表

高16位 与 哈希值异或操作
Node 存放 key ,value

sizeCtl:
负数:表示进行初始化或者扩容,-1 表示正在初始化 -n 表示有n-1 个线程正在进行扩容
正数:0 表示还没有被初始化。 >0 初始化 或者 下一次扩容的阈值

TreeNode
TreeBin 实际放在table数组中的,代表了这个红黑树的根,

//Node 存放 key ,value
static class Node<K,V> implements Map.Entry<K,V> {
				//有以下元素
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

put

//  put 方法里 对数组进行初始化
private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            //循环设置  sizeCtl
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                //数组 初始化
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);// 0.75N
                    }
                } finally {
                		//控制 是否需要扩容
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }




-----------------------------------------------------------------------------------------



public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        // key 的哈希值再散列
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
                // table 数组为 null 时,直接把key 放在数组里面,作为数组元素的一部分
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                        	//红黑树插入数据
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                //如果 链表长度 大于阈值 , 
                    if (binCount >= TREEIFY_THRESHOLD)
                    //由链表转为树
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

扩容操作
*2 的好处 : 可以快速定位和减少重排次数

private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            // 当当前容量大于 扩容阈值
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                int rs = resizeStamp(n);
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }



//实际扩容方法     翻倍的形式
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
            //控制最大容量
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

帮助扩容
与hashMap的不同
帮助扩容,并发扩容的机制
当线程进来put ,发现有别的线程在扩容,这个线程会帮助扩容,扩容完成以后,再put

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

get

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //table 这个数组中是我指定的key 直接返回
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            //在红黑树中寻找
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            //在链表中寻找
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

size
弱一致性,因为 CounterCell 里的数据 可能不是最新的;
所以是一个大概数量,不是一个精确数量;

public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }
    

final long sumCount() {
				//counterCells 是 还没来得及统计进 baseCount 的数量
        CounterCell[] as = counterCells; CounterCell a;
        //baseCount  当前数量
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

更多的并发容器

ConcurrentSkipListMap 、 ConcurrentSkipListSet
TreeMap 和 TrssSet 有序的容器,这两种容器的并发版本;

了解什么是SkipList
跳表 、 SkipList
以空间换时间、相当于索引
再插入的时候取一个随机数,如果满足条件 就把当前插入元素作为索引;
概率数据结构
在这里插入图片描述

实现上比红黑树简单,但是查询效率非常接近红黑树

ConcurrentLinkedQueue
LinkedList 的 并发版本
无界非阻塞队列,底层是个链表,遵循先进先出原则;

add ,offer 方法 将元素插入到尾部,
peek(拿头部的数据,但是不移除) 、poll(拿头部的数据,并移除)

写时复制容器
CopyOnWriteArrayList 、 CopyOnWriteArraySet
读写分离的一种思想
往一个容器添加元素的时候,先把当前容器拷贝出来,往新容器里添加元素
再将原来容器的引用指向新的容器;
只能保证最终一致性,不能保证实时一致性;
适合读多写少的并发场景,白名单/黑名单
内存占用,

阻塞队列
概念,生产者消费者模式
当队列满的时候,插入元素的线程被阻塞,直到队列不满。

队列为空的时候,获取元素的线程就会被阻塞,直到队列不空

生产者消费者模式,能力不匹配的问题,加一个容器解决生产者和消费者直接的耦合;

常用方法

方法抛出异常返回值一直阻塞超时退出
插入方法addofferputoffer(time)
移除方法removepolltakepool(time)
检查方法elementpeekN/AN/A

常用阻塞队列

BlockingQueue
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
按照先进先出原则排序、要求设定初始大小

LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列
要找先进先出原则,可以不设定初始大小,默认 Integer.MAX_Value
区别:
锁上:ArrayBlockingQueue 只有一个锁,LinkedBlockingQueue 用了两个锁,
实现上 : ArrayBlockingQueue 直接插入元素,LinkedBlockingQueue 需要转换


PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
默认情况下,按照自然顺序,要么实现 compareTo()方法,指定构造参数Comparetor();

DelayQueue 一个使用优先级队列实现的无界阻塞队列
支持延时获取元素的阻塞队列,元素必须要实现 Delayed 接口,
实现自己的缓冲系统;订单到期,显示支付

SynchronousQueue 一个不存储元素的阻塞队列
每一个put操作都要等待一个take操作

LinkedTransferQueue 一个由链表结构组成的无界阻塞队列
transfer( ) ,看有没有消费者在等待,如果有,直接传给消费者,否则,放入队列; 必须要消费者消费了以后才会返回(哪怕是放入了队列);
tryTransfer( ) 无论消费者是否接收,都会立即返回;
LinkedBlockingDeque 一个由链表组成的双向阻塞队列
可以从队列的头和尾都可以插入和移除元素,
实现工作密取,方法名带了first 从头存取,带了last 存尾拿取
add = addLast , remove = removeFirst, take = takeFirst

尽量使用有界的,否则容易被撑爆
DelayQueue代码示例

/**
 * 存放到元素的队列
 *
 * @param <T>
 */
public class ItemVo<T> implements Delayed {

    //到期时长  单位 ms
    private long activeTime;
    private T date;

    public ItemVo(long activeTime, T date) {
        //将传入的时长转换为超时的时刻
        this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS) + System.nanoTime();
        this.date = date;
    }

    public long getActiveTime() {
        return activeTime;
    }

    public T getDate() {
        return date;
    }

    /**
     * 返回元素的剩余时间
     *
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        long d = unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
        return d;
    }

    /**
     * 按照剩余时间排序
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return d == 0 ? 0 : (d > 0 ? 1 : -1);
    }
}




/**
 * 订单实体类
 */
public class Order {

    private final String orderNo;
    private final double orderMoney;


    public Order(String orderNo, double orderMoney) {
        this.orderNo = orderNo;
        this.orderMoney = orderMoney;
    }

    public String getOrderNo() {
        return orderNo;
    }

    public double getorderMoney() {
        return orderMoney;
    }
}



/**
 * 添加订单
 */
public class PutOrder implements Runnable {

    private DelayQueue<ItemVo<Order>> queue;

    public PutOrder(DelayQueue<ItemVo<Order>> queue) {
        super();
        this.queue = queue;
    }

    @Override
    public void run() {

        // 5 s 到期
        Order order = new Order("tb",12.32);
        ItemVo<Order> itemVo = new ItemVo<Order>(5000,order);
        queue.offer(itemVo);

        System.out.println("订单 5s 后到期" + order.getOrderNo());


        //8s到期
        Order order1 = new Order("jd",12.34);
        ItemVo<Order> itemVo1 = new ItemVo<Order>(8000,order1);
        queue.offer(itemVo1);

        System.out.println("订单 8s 后到期" + order1.getOrderNo());
    }
}




/**
 * 取订单
 */
public class FetchOrder implements Runnable {

    private DelayQueue<ItemVo<Order>> queue;

    public FetchOrder(DelayQueue<ItemVo<Order>> queue) {
        super();
        this.queue = queue;
    }


    @Override
    public void run() {
        while (true){
            try {
                ItemVo<Order> itemVo = queue.take();
                Order order = (Order) itemVo.getDate();
                System.out.println("取出  " + order.getOrderNo());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class Test {

    public static void main(String[] args) throws InterruptedException {

        DelayQueue<ItemVo<Order>> delayQueue = new DelayQueue<>();
        new Thread(new PutOrder(delayQueue)).start();
        new Thread(new FetchOrder(delayQueue)).start();

        for (int i = 0; i < 15; i++) {
            Thread.sleep(500);
            System.out.println(i * 500);
        }
    }
}

阻塞队列的实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值