并发容器(ConcurrentHashMap,ConcurrentSkipListMap,CopyOnWriteArrayList)

ConcurrentHashMap

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

HashMap在多线程put操作时会引起死循环,hashmap里的entry链表产生环形数据结构。关于环形链表的形成,则主要在这扩容的过程。当多个线程同时对这个HashMap进行put操作,而察觉到内存容量不够,需要进行扩容时,多个线程会同时执行resize操作,而这就出现问题了,问题的原因分析如下:
在HashMap扩容时,会改变链表中的元素的顺序,将元素从链表头部插入,而环形链表就在这一时刻发生,以下模拟2个线程同时扩容

  • 线程一:读取到当前的hashmap情况,在准备扩容时,线程二介入
    在这里插入图片描述
  • 线程二:读取hashmap,进行扩容
    在这里插入图片描述
  • 线程一:继续执行,先将A复制到新的hash表中,然后接着复制B到链头(A的前边:B.next=A),本来B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将B.next=A,所以,这里继续复制A,让A.next=B,由此,环形链表出现:B.next=A; A.next=B

在这里插入图片描述

【位运算】知识补充:

public class Permission {
	
    // 是否允许查询,二进制第1位,0表示否,1表示是  
    public static final int ALLOW_SELECT = 1 << 0; // 0001  = 1
      
    // 是否允许新增,二进制第2位,0表示否,1表示是  
    public static final int ALLOW_INSERT = 1 << 1; // 0010  = 2
      
    // 是否允许修改,二进制第3位,0表示否,1表示是  
    public static final int ALLOW_UPDATE = 1 << 2; // 0100  =4
      
    // 是否允许删除,二进制第4位,0表示否,1表示是  
    public static final int ALLOW_DELETE = 1 << 3; // 1000  = 8
    // 存储目前的权限状态  
    private int flag; 
    //设置用户的权限
    public void setPer(int per) {
    	flag = per;
    }
    //增加用户的权限(1个或者多个)
    public void enable(int per) {
    	flag = flag|per;
    }
  //删除用户的权限(1个或者多个)
    public void disable(int per) {
    	flag = flag&~per;
    }
    //判断用户的权限
    public boolean isAllow(int per) {
    	return ((flag&per)== per);
    }
    //判断用户没有的权限
    public boolean isNotAllow(int per) {
    	return ((flag&per)==0);
    }
  
    public static void main(String[] args) {
		int flag = 15;
		Permission permission = new Permission();
		permission.setPer(flag);
		permission.disable(ALLOW_DELETE|ALLOW_INSERT);
		System.out.println("select = "+permission.isAllow(ALLOW_SELECT));
		System.out.println("update = "+permission.isAllow(ALLOW_UPDATE));
		System.out.println("insert = "+permission.isAllow(ALLOW_INSERT));
		System.out.println("delete = "+permission.isAllow(ALLOW_DELETE));
	}
}

【执行结果】:

select = true
update = true
insert = false
delete = false

基本成员变量

	/** node数组的最大容量 2^30 */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

   /** 默认初始化值16,必须是2的冥 */
    private static final int DEFAULT_CAPACITY = 16;

   /** 虚拟机限制的最大数组长度,在ArrayList中有说过,jdk1.8新引入的,需要与toArrar()相关方法关联 */
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /** 负载因子,兼容以前版本,构造方法中指定的参数是不会被用作loadFactor的,为了计算方便,统一使用 n - (n >> 2) 代替浮点乘法 *0.75 */
    private static final float LOAD_FACTOR = 0.75f;

    /** 链表转红黑树,阈值>=8 */
    static final int TREEIFY_THRESHOLD = 8;

    /** 树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,
     *  <=UNTREEIFY_THRESHOLD 则untreeify(lo))
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /** 链表转红黑树的阈值,64(map容量小于64时,链表转红黑树时先进行扩容) */
    static final int MIN_TREEIFY_CAPACITY = 64;

/** 下面这三个和多线程协助扩容有关 */

   /** // 扩容操作中,transfer这个步骤是允许多线程的,这个常量表示一个线程执行transfer时,最少要对连续的16个hash桶进行transfer
    //     (不足16就按16算,多控制下正负号就行)
    private static final int MIN_TRANSFER_STRIDE = 16;

    /** 生成sizeCtl所使用的bit位数 */
    private static int RESIZE_STAMP_BITS = 16;

    /** 参与扩容的最大线程数 */
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

    /**  移位量,把生成戳移位后保存在sizeCtl中当做扩容线程计数的基数,相反方向移位后能够反解出生成
     戳 */
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

    /*
     * Encodings for Node hash fields. See above for explanation.
     */
    static final int MOVED     = -1; // 表示正在转移
    static final int TREEBIN   = -2; // 表示已经转换为树
    static final int RESERVED  = -3; // hash for transient reservations
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

    /** 可用处理器数量 */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

         /** 用于存放node数组 */
    transient volatile Node<K,V>[] table;

        /**
     * baseCount为并发低时,直接使用cas设置成功的值
     * 并发高,cas竞争失败,把值放在counterCells数组里面的counterCell里面
     * 所以map.size = baseCount + (每个counterCell里面的值累加)
     */
    private transient volatile long baseCount;

        /**
     * 控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
     * 当为负数时:-1代表正在初始化,-N就代表在扩容,-N-RS-2就代表有多少个线程在协助扩容
     * 当为0时:代表当时的table还没有被初始化
     * 当为正数时:表示初始化或者下一次进行扩容的大小
     */
    private transient volatile int sizeCtl;

        /**
     * 通过cas实现的锁,0 无锁,1 有锁
     */
    private transient volatile int cellsBusy;

    /**
     * counterCells数组,具体的值在每个counterCell里面
     */
    private transient volatile CounterCell[] counterCells;

基本操作方法

  • 构造方法
	/**
     * 指定初始化大小的构造,不能小于0
     * @param initialCapacity
     */
    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        // cap必须是2的n次方,
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                MAXIMUM_CAPACITY :
                tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }
  • 初始化方法
private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) { // 空的table 才能初始化
            if ((sc = sizeCtl) < 0) // 表示其它线程正在初始化或者扩容
                // 当前线程把执行权交给其它线程(拥有相同优先级的线程),然后变成可运行状态
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 原子操作表示把SIZECTL设置为-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); // 下一次扩容阈值 n*0,75
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }
  • put方法
  1. 我们可以通过源码判断keyvalue不允许为null。
  2. 需要判断table有没有初始化,没有调用initTable初始化,然后接着循环。
  3. 判断key的hash(调用spread方法)的位置有没有值,证明是第一个,使用cas设置,为什么cas,可能不止一个线程。
  4. 判断当前线程的hash是不是MOVED,其实就是节点是不是ForwardingNode节点,ForwardingNode代表正在扩容,至于为什么会是ForwardingNode,这个在扩容的方法里面再讲,如果是ForwardingNode节点就协助扩容,也就是当前也去扩容,然后扩容完毕,在执行循环,协助扩容执行helpTransfer方法。
  5. 如果不是扩容、table也初始化了和hash位置也有值了,那证明当前hash的位置是链表或者树,接下来锁住这个节点,进行链表或者树的节点的追加,如果存在相同的key,就替换,最后释放锁。
  6. 判断链表的节点数,有没有大于等于8,满足就树化,调用treeifyBin方法,这个方法会在树化前判断大于等于64吗,没有就扩容,调用tryPresize方法,有就树化。
  7. 修改节点的数量,调用addCount方法。
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) {
        // 不允许key或者value 为null
        if (key == null || value == null) throw new NullPointerException();
        // 获取hash
        int hash = spread(key.hashCode());
        int binCount = 0;
        // 遍历table,死循环,直到插入成功
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0) // table 还没有初始化
                tab = initTable(); // 初始化
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 当前位置为空 ,直接插入
                if (casTabAt(tab, i, null, // 使用cas来进行设置
                        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) { // 普通Node的hash值为key的hash值大于零,而ForwardingNode的是-1,TreeBin是-2
                            binCount = 1;
                            // 遍历链表
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                                (ek != null && key.equals(ek)))) { // 找到了相同的key
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value; // 替换value 结束循环
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) { // 找到最后一个节点
                                    pred.next = new Node<K,V>(hash, key,
                                            value, null); // 把当前节点设置为最后一个节点的next
                                    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) // 如果链表大于等于8,树化
                        treeifyBin(tab, i); // 树化
                    if (oldVal != null) // 证明存在相同的key,是替换return旧值
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount); //数量加1
        return null;
    }
  • get方法
	// get方法
    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        // 获取hash值
        int h = spread(key.hashCode());
        // table不为null,table已经初始化,通过hash查找的node不为nul
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) { // hash相等
                if ((ek = e.key) == key || (ek != null && key.equals(ek))) // 找到了相同的key
                    return e.val; // 返回当前e的value
            }
            else if (eh < 0) // hash小于0,说明是特殊节点(TreeBin或ForwardingNode)调用find
                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;
    }
  • transfer扩容方法
  1. stride这个参数其实就是算每个线程处理的数量,和CPU有关,最小是16.
  2. 初始化一个原来二倍的新table就是 nextTable,然后这个过程可能会出错,n<<1可能为负数,设置nextTable和transferIndex,其中transferIndex就是原table的长度。
  3. 初始化一个ForwardingNode节点在后面会用到。
  4. 死循环for,这个循环就是为每个线程分配任务,然后每个线程处理各自的任务,倒叙分配,举个例子,加入table.length=32,现在的stride为16,第一个线程其实就是32到16(不包含32,因为是索引),第二个线程就是0-15,参考这一段代码((U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0)))),然后遍历每个段,处理节点,知道处理完成,具体逻辑参考代码注释。
	/**
     * 扩容方法
     */
    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        // n >>> 3(也就是除以8) / cpu个数,每个cpu的每个线程负责的迁移的数量
        // 这样的目的是为了每个cpu处理的桶一样多,避免出现任务转移不均匀的现象,如果桶少的话,默认一个cpu(一个线程)处理16个桶
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        // 扩容table 没有初始化
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //  初始化原来的length两倍的table
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                // 初始化失败,使用integer的最大值
                sizeCtl = Integer.MAX_VALUE;
                return; // 结束
            }
            // 更新成员变量
            nextTable = nextTab;
            // 更新转移下标,就是运来的table的length
            transferIndex = n;
        }
        // 新table的length
        int nextn = nextTab.length;
        // 创建一个fwd节点,用于占位.当别的节点发现这个槽位中有fwd节点时,则跳过这个节点
        // 它的hash为MOVED
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        // 首次推进为true,如果为true说明需要再次推进一个目标(i--),反之如果是false,那么就不能推进下标,需要将当前的下标处理完毕
        boolean advance = true;
        // 完成状态,如果为true,就结束方法
        boolean finishing = false; // to ensure sweep before committing nextTab
        // 死循环,因为是倒着遍历,所以i是点前线程的最大位置(i---),bound是边界,也就是区间里面的最小值
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            // 如果当前线程可以向后推进,这个循环就是控制i递减.同时每个线程都会进入这里取得自己需要转移的桶的下标区间
            // 1. true
            while (advance) {
                int nextIndex, nextBound;
                // 1. -1 >= 0,false
                if (--i >= bound || finishing)
                    advance = false;
                    //transferIndex <= 0 说明已经没有需要迁移的桶了
                // 1.nextIndex = 16 <= 0
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //更新 transferIndex
                //为当前线程分配任务,处理的桶结点区间为(nextBound,nextIndex)
                // 1.16 > 16 ? 16 -16 : 0 区间 16 到0
                else if (U.compareAndSwapInt
                        (this, TRANSFERINDEX, nextIndex,
                                nextBound = (nextIndex > stride ?
                                        nextIndex - stride : 0))) {
                    bound = nextBound; // 0
                    i = nextIndex - 1;// 15
                    advance = false;
                }
            }
            // i = 15 nextn = 32

            // i < 0 ,表示数据迁移已经完成
            // i >= n 和 i + n >= nextn 表示最后一个线程也执行完成了,扩容完成了
            //  第二个if里面的i=n
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) { // 完成扩容
                    nextTable = null; // 删除成员变量
                    table = nextTab; // 更新table
                    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
                }
            }
            // 待迁移桶为null,用cas把当前节点设置为ForwardingNode节点,表示已经处理
            else if ((f = tabAt(tab, i)) == null) //  第一个线程 获取i处的数据为null,
                advance = casTabAt(tab, i, null, fwd);// 设置当前节点为 fwd 节点
            else if ((fh = f.hash) == MOVED) // 如果当前节点为 MOVED,说明已经处理过了,直接跳过
                advance = true; // already processed
            else {
                // 节点不为空,锁住i位置的头结点
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) { //  表示是链表
                            int runBit = fh & n; // fn表示f.hash & n ,表示获取原来table的位置
                            Node<K,V> lastRun = f; // 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; // 当前节点
                                }
                            }
                            // 不管runBit有没有发生变化,只可能是0或者n,
                            // ln表示的不变化的节点
                            // hn表示的是变化节点的位置
                            if (runBit == 0) { // 如果是0,那么ln=lastRun就是位置没有变的这条链 hn=null变化链需要遍历重组
                                ln = lastRun;
                                hn = null;
                            }
                            else { // 如果当前节点不是0,hn=lastRun这个变化链,ln=null没有变化的链需要遍历重组
                                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);
                           // 原来table的位置设置fwd节点,表示扩容
                            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;
                        }
                    }
                }
            }
        }
    }
  • size方法
public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                        (int)n);
    }

        final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        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

ConcurrentSkipListMapConcurrentSkipListSet是TreeMap和TreeSet的有序容器的并发版本

ConcurrentSkipListMap的底层是通过跳表来实现的。跳表(Skiplist)是一个链表,但是通过使用“跳跃式”查找的方式使得插入、读取数据时复杂度变成了O(logn),跳表以空间换时间,概率数据结构

在这里插入图片描述

  • 插入操作doPut
    private V doPut(K kkey, V value, boolean onlyIfAbsent) {
    Comparable<? super K> key = comparable(kkey);
    for (;;) {
        // 找到key的前继节点
        Node<K,V> b = findPredecessor(key);
        // 设置n为“key的前继节点的后继节点”,即n应该是“插入节点”的“后继节点”
        Node<K,V> n = b.next;
        for (;;) {
            if (n != null) {
                Node<K,V> f = n.next;
                // 如果两次获得的b.next不是相同的Node,就跳转到”外层for循环“,重新获得b和n后再遍历。
                if (n != b.next)
                    break;
                // v是“n的值”
                Object v = n.value;
                // 当n的值为null(意味着其它线程删除了n);此时删除b的下一个节点,然后跳转到”外层for循环“,重新获得b和n后再遍历。
                if (v == null) {               // n is deleted
                    n.helpDelete(b, f);
                    break;
                }
                // 如果其它线程删除了b;则跳转到”外层for循环“,重新获得b和n后再遍历。
                if (v == n || b.value == null) // b is deleted
                    break;
                // 比较key和n.key
                int c = key.compareTo(n.key);
                if (c > 0) {
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) {
                    if (onlyIfAbsent || n.casValue(v, value))
                        return (V)v;
                    else
                        break; // restart if lost race to replace value
                }
                // else c < 0; fall through
            }

            // 新建节点(对应是“要插入的键值对”)
            Node<K,V> z = new Node<K,V>(kkey, value, n);
            // 设置“b的后继节点”为z
            if (!b.casNext(n, z))
                break;         // 多线程情况下,break才可能发生(其它线程对b进行了操作)
            // 随机获取一个level
            // 然后在“第1层”到“第level层”的链表中都插入新建节点
            int level = randomLevel();
            if (level > 0)
                insertIndex(z, level);
            return null;
        }
    }
}
  • 删除操作doRemove
final V doRemove(Object okey, Object value) {
    Comparable<? super K> key = comparable(okey);
    for (;;) {
        // 找到“key的前继节点”
        Node<K,V> b = findPredecessor(key);
        // 设置n为“b的后继节点”(即若key存在于“跳表中”,n就是key对应的节点)
        Node<K,V> n = b.next;
        for (;;) {
            if (n == null)
                return null;
            // f是“当前节点n的后继节点”
            Node<K,V> f = n.next;
            // 如果两次读取到的“b的后继节点”不同(其它线程操作了该跳表),则返回到“外层for循环”重新遍历。
            if (n != b.next)                    // inconsistent read
                break;
            // 如果“当前节点n的值”变为null(其它线程操作了该跳表),则返回到“外层for循环”重新遍历。
            Object v = n.value;
            if (v == null) {                    // n is deleted
                n.helpDelete(b, f);
                break;
            }
            // 如果“前继节点b”被删除(其它线程操作了该跳表),则返回到“外层for循环”重新遍历。
            if (v == n || b.value == null)      // b is deleted
                break;
            int c = key.compareTo(n.key);
            if (c < 0)
                return null;
            if (c > 0) {
                b = n;
                n = f;
                continue;
            }

            // 以下是c=0的情况
            if (value != null && !value.equals(v))
                return null;
            // 设置“当前节点n”的值为null
            if (!n.casValue(v, null))
                break;
            // 设置“b的后继节点”为f
            if (!n.appendMarker(f) || !b.casNext(n, f))
                findNode(key);                  // Retry via findNode
            else {
                // 清除“跳表”中每一层的key节点
                findPredecessor(key);           // Clean index
                // 如果“表头的右索引为空”,则将“跳表的层次”-1。
                if (head.right == null)
                    tryReduceLevel();
            }
            return (V)v;
        }
    }
}
  • 查找操作findNode
private Node<K,V> findNode(Comparable<? super K> key) {
    for (;;) {
        // 找到key的前继节点
        Node<K,V> b = findPredecessor(key);
        // 设置n为“b的后继节点”(即若key存在于“跳表中”,n就是key对应的节点)
        Node<K,V> n = b.next;
        for (;;) {
            // 如果“n为null”,则跳转中不存在key对应的节点,直接返回null。
            if (n == null)
                return null;
            Node<K,V> f = n.next;
            // 如果两次读取到的“b的后继节点”不同(其它线程操作了该跳表),则返回到“外层for循环”重新遍历。
            if (n != b.next)                // inconsistent read
                break;
            Object v = n.value;
            // 如果“当前节点n的值”变为null(其它线程操作了该跳表),则返回到“外层for循环”重新遍历。
            if (v == null) {                // n is deleted
                n.helpDelete(b, f);
                break;
            }
            if (v == n || b.value == null)  // b is deleted
                break;
            // 若n是当前节点,则返回n。
            int c = key.compareTo(n.key);
            if (c == 0)
                return n;
            // 若“节点n的key”小于“key”,则说明跳表中不存在key对应的节点,返回null
            if (c < 0)
                return null;
            // 若“节点n的key”大于“key”,则更新b和n,继续查找。
            b = n;
            n = f;
        }
    }
}

通过上面的源码可以发现:ConcurrentSkipListMap线程安全的原理与非阻塞队列ConcurrentBlockingQueue的原理一样:利用底层的插入、删除的CAS原子性操作,通过死循环不断获取最新的结点指针来保证不会出现竞态条件

CopyOnWriteArrayList

CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组,合适读多写少的场景

当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的
在这里插入图片描述
当元素在新数组添加成功后,将array这个引用指向新数组
在这里插入图片描述
CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。 这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的

add操作的源代码:

 public boolean add(E e) {
    //1、先加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        //2、拷贝数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //3、将元素加入到新数组中
        newElements[len] = e;
        //4、将array引用指向到新数组
        setArray(newElements);
        return true;
    } finally {
       //5、解锁
        lock.unlock();
    }
}

CopyOnWriteArrayList 有几个缺点:

  1. 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc
  2. 不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值