精华内容
下载资源
问答
  • Java BlockingQueue 源码分析

    千次阅读 2015-05-03 01:16:42
    Java BlockingQueue 源码分析

    简介

    BlockingQueue 是 Java concurrent包提供的多线程安全的阻塞队列,其子类包括 LinkedBlockingQueue 和 ArrayBlockingQueue。

    关键API

    说到队列,自然少不了首尾的插入删除操作,BlockingQueue的API中提供了好几种插入删除方法。
    这些方法在遇到无法满足的执行条件时,如队列满了(添加元素时)/队列为空(取出元素时),会采取不同的措施:抛出异常,返回false/null,阻塞调用API的线程,等待一定时间等。具体如下表:

    Throws exception Special value Blocks Times out
    Insert add(e) offer(e) put(e) offer(e,time,unit)
    Remove remove() poll() take() poll(time,unit)
    Examine element() peek() not applicable not applicable

    ArrayBlockingQueue

    ArrayBlockingQueue是一个基于数组的阻塞队列,在创建一个ArrayBlockingQueue时,需要提供的一个表示队列大小的参数。

    ArrayBlockingQueue是线程安全的,但这是怎么做到的呢?这需要注意类中的三个属性:

        /** Main lock guarding all access */
        final ReentrantLock lock;
        /** Condition for waiting takes */
        private final Condition notEmpty;
        /** Condition for waiting puts */
        private final Condition notFull;

    这里一个锁,两个条件变量,管理所有使用API的线程的互斥和同步。具体的锁和条件变量的理论知识,可以参见相关的操作系统书籍。

    offer vs put

        public boolean offer(E e) {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if (count == items.length)
                    return false;
                else {
                    insert(e);
                    return true;
                }
            } finally {
                lock.unlock();
            }
        }
    
        public void put(E e) throws InterruptedException {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                while (count == items.length)
                    notFull.await();
                insert(e);
            } finally {
                lock.unlock();
            }
        }

    可以看到,两个方法中在往队列里添加元素时,都是先对临界区加锁,不同在于,offer方法中若是检测出队列已满,会直接返回false;put方法中,若是检测出队列已满,线程会在 notFull 条件变量中阻塞,这样线程会释放锁,让其他线程进入临界区。以后某个时间,一个线程从队列中取出元素,队列不再为空,并且该线程唤醒在notFull条件变量上阻塞的线程,put方法才有可能完成。

    poll vs take

     public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                while (count == 0)
                    notEmpty.await();
                return extract();
            } finally {
                lock.unlock();
            }
        }

    take的逻辑和put差不多,只不过这次是若队列为空,则线程阻塞在notEmpty 条件变量上,等待其他线程往队列中添加元素,并唤醒在notEmpty上阻塞的线程。

       public E poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return (count == 0) ? null : extract();
            } finally {
                lock.unlock();
            }
        }

    poll方法在检测到队列为空时,直接返回null,否则取出队头元素。

          public E poll(long timeout, TimeUnit unit) throws InterruptedException {
            long nanos = unit.toNanos(timeout);
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                while (count == 0) {
                    if (nanos <= 0)
                        return null;
                    nanos = notEmpty.awaitNanos(nanos);
                }
                return extract();
            } finally {
                lock.unlock();
            }
        }
    

    poll 还有个比较有趣的重载实现,这里利用Condition变量的计时器方法awaitNanos 。先将时间大小根据时间单位换算成纳秒的数值,当队列容量为0是,使用Condition.awaitNanos(…),进行计时,超时后返回null。

    展开全文
  • Java源码分析》:ConcurrentHashMap JDK1.8

    万次阅读 多人点赞 2016-08-07 21:42:41
    Java源码分析》:ConcurrentHashMap JDK1.8最近一直在看关于J.U.C中的源码,了解原子操作,了解锁机制,了解多线程并发等等。但是ConcurrentHashMap一直拖着到今天才算告一段落。也要感谢ConcurrentHashMap这个类...

    《Java源码分析》:ConcurrentHashMap JDK1.8

    最近一直在看关于J.U.C中的源码,了解原子操作,了解锁机制,了解多线程并发等等。但是ConcurrentHashMap一直拖着到今天才算告一段落。

    也要感谢ConcurrentHashMap这个类,刚开始就是想弄懂里面的工作原理,但是,无奈看了网上关于介绍ConcurrentHashMap这个类的资料或博客都是基于JDK1.8以前的,而刚好此类在JDK1.8之后有很大的变化。因此,由于里面涉及到关于原子操作CAS,自己以前并不知道是什么,于是就开始对原子操作进行了解,看了java.util.concurrent.atom包下相关类源码对其有了一定的了解。接着为了了解锁机制,看了java.util.concurrent.lock包下相关的类库,对锁机制有了大概的了解之后,看了线程池相关的类,对线程池也有了一定的了解。

    关于阻塞队列相关的类,自己也大致看了下,但是并没有形成相应的博文,以后有时间重新来了解他们的时候才记录吧。整个过程大概花费了我将近一个来月的时间,虽然对看过的类库的内部实现都只是一个大致的了解,但是确实收获还是挺多的。让我们更好的明白在多线程并发中他们是如何来工作的。

    回到正题,刚好借着今天星期天,花了将近一天的时间来看ConcurrentHashMap的实现原理,总算看了一个大概,有了一个大致的了解。也就有了这篇博文。

    ConcurrentHashMap 在JDK1.8版本以前的实现原理

    既然本篇博文的标题明确的标出了是基于JDK1.8版本的,也就暗示了这个版本和以前的版本关于ConcurrentHashMap有些许的不同,对吧。x

    下面我们就先借助网上的资料来看下以前版本的ConcurrentHashMap的实现思路。

    我们都知道HashMap是线程不安全的。Hashtable是线程安全的。看过Hashtable源码的我们都知道Hashtable的线程安全是采用在每个方法来添加了synchronized关键字来修饰,即Hashtable是针对整个table的锁定,这样就导致HashTable容器在竞争激烈的并发环境下表现出效率低下。

    效率低下的原因说的更详细点:是因为所有访问HashTable的线程都必须竞争同一把锁。当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

    基于Hashtable的缺点,人们就开始思考,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率呢??这就是我们的“锁分离”技术,这也是ConcurrentHashMap实现的基础。

    ConcurrentHashMap使用的就是锁分段技术,ConcurrentHashMap由多个Segment组成(Segment下包含很多Node,也就是我们的键值对了),每个Segment都有把锁来实现线程安全,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    因此,关于ConcurrentHashMap就转化为了对Segment的研究。这是因为,ConcurrentHashMap的get、put操作是直接委托给Segment的get、put方法,但是自己上手上的JDK1.8的具体实现确不想网上这些博文所介绍的。因此,就有了本篇博文的介绍。

    推荐几个JDK1.8以前版本的关于ConcurrentHashMap的原理分析,方便大家比较。

    1、http://www.iteye.com/topic/344876

    2、http://ifeve.com/concurrenthashmap/

    如需要更多,请自己网上搜索即可。

    下面就开始JDK1.8版本中ConcurrentHashMap的介绍。

    JDK1.8 版本中ConcurrentHashMap介绍

    1、前言

    首先要说明的几点:

    1、JDK1.8的ConcurrentHashMap中Segment虽保留,但已经简化属性,仅仅是为了兼容旧版本。

    2、ConcurrentHashMap的底层与Java1.8的HashMap有相通之处,底层依然由“数组”+链表+红黑树来实现的,底层结构存放的是TreeBin对象,而不是TreeNode对象;

    3、ConcurrentHashMap实现中借用了较多的CAS算法,unsafe.compareAndSwapInt(this, valueOffset, expect, update); CAS(Compare And Swap),意思是如果valueOffset位置包含的值与expect值相同,则更新valueOffset位置的值为update,并返回true,否则不更新,返回false。

    ConcurrentHashMap既然借助了CAS来实现非阻塞的无锁实现线程安全,那么是不是就没有用锁了呢??答案:还是使用了synchronized关键字进行同步了的,在哪里使用了呢?在操作hash值相同的链表的头结点还是会synchronized上锁,这样才能保证线程安全。

    看完ConcurrentHashMap整个类的源码,给自己的感觉就是和HashMap的实现基本一模一样,当有修改操作时借助了synchronized来对table[i]进行锁定保证了线程安全以及使用了CAS来保证原子性操作,其它的基本一致,例如:ConcurrentHashMap的get(int key)方法的实现思路为:根据key的hash值找到其在table所对应的位置i,然后在table[i]位置所存储的链表(或者是树)进行查找是否有键为key的节点,如果有,则返回节点对应的value,否则返回null。思路是不是很熟悉,是不是和HashMap中该方法的思路一样。所以,如果你也在看ConcurrentHashMap的源码,不要害怕,思路还是原来的思路,只是多了些许东西罢了。

    2、ConcurrentHashMap类中相关属性的介绍

    为了方便介绍此类后面的实现,这里需要先将此类中的一些属性给介绍下。

    sizeCtl最重要的属性之一,看源码之前,这个属性表示什么意思,一定要记住。

    0、private transient volatile int sizeCtl;//控制标识符

    此属性在源码中给出的注释如下:

         /**
            * Table initialization and resizing control.  When negative, the
            * table is being initialized or resized: -1 for initialization,
            * else -(1 + the number of active resizing threads).  Otherwise,
            * when table is null, holds the initial table size to use upon
            * creation, or 0 for default. After initialization, holds the
            * next element count value upon which to resize the table.
            */

    翻译如下:

    sizeCtl是控制标识符,不同的值表示不同的意义。

    • 负数代表正在进行初始化或扩容操作 ,其中-1代表正在初始化 ,-N 表示有N-1个线程正在进行扩容操作
    • 正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小,类似于扩容阈值。它的值始终是当前ConcurrentHashMap容量的0.75倍,这与loadfactor是对应的。实际容量>=sizeCtl,则扩容。

    1、 transient volatile Node<K,V>[] table;是一个容器数组,第一次插入数据的时候初始化,大小是2的幂次方。这就是我们所说的底层结构:”数组+链表(或树)”

    2、private static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量

    3、private static final intDEFAULT_CAPACITY = 16;

    4、static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // MAX_VALUE=2^31-1=2147483647

    5、private static finalint DEFAULT_CONCURRENCY_LEVEL = 16;

    6、private static final float LOAD_FACTOR = 0.75f;

    7、static final int TREEIFY_THRESHOLD = 8; // 链表转树的阀值,如果table[i]下面的链表长度大于8时就转化为数

    8、static final int UNTREEIFY_THRESHOLD = 6; //树转链表的阀值,小于等于6是转为链表,仅在扩容tranfer时才可能树转链表

    9、static final int MIN_TREEIFY_CAPACITY = 64;

    10、private static final int MIN_TRANSFER_STRIDE = 16;

    11、private static int RESIZE_STAMP_BITS = 16;

    12、private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; // help resize的最大线程数

    13、private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

    14、static final int MOVED = -1; // hash for forwarding nodes(forwarding nodes的hash值)、标示位

    15、static final int TREEBIN = -2; // hash for roots of trees(树根节点的hash值)

    16、static final int RESERVED = -3; // hash for transient reservations(ReservationNode的hash值)

    3、ConcurrentHashMap的构造函数

    和往常一样,我们还是从类的构造函数开始说起。

        /**
         * Creates a new, empty map with the default initial table size (16).
         */
        public ConcurrentHashMap() {
        }
    
        public ConcurrentHashMap(int initialCapacity) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException();
            int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
            this.sizeCtl = cap;
        }
    
        /*
         * Creates a new map with the same mappings as the given map.
         *
         */
        public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
            this.sizeCtl = DEFAULT_CAPACITY;
            putAll(m);
        }
    
        public ConcurrentHashMap(int initialCapacity, float loadFactor) {
            this(initialCapacity, loadFactor, 1);
        }
        public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel) {
            if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
                throw new IllegalArgumentException();
            if (initialCapacity < concurrencyLevel)   // Use at least as many bins
                initialCapacity = concurrencyLevel;   // as estimated threads
            long size = (long)(1.0 + (long)initialCapacity / loadFactor);
            int cap = (size >= (long)MAXIMUM_CAPACITY) ?
                MAXIMUM_CAPACITY : tableSizeFor((int)size);
            this.sizeCtl = cap;
        }

    有过HashMap和Hashtable源码经历,看这些构造函数是不是相当easy哈。

    上面的构造函数主要干了两件事:

    1、参数的有效性检查

    2、table初始化的长度(如果不指定默认情况下为16)。

    这里要说一个参数:concurrencyLevel,表示能够同时更新ConccurentHashMap且不产生锁竞争的最大线程数。默认值为16,(即允许16个线程并发可能不会产生竞争)。为了保证并发的性能,我们要很好的估计出concurrencyLevel值,不然要么竞争相当厉害,从而导致线程试图写入当前锁定的段时阻塞。

    ConcurrentHashMap类中相关节点类:Node/TreeNode/TreeBin

    1、Node类

    Node类是table数组中的存储元素,即一个Node对象就代表一个键值对(key,value)存储在table中。

    Node类是没有提供修改入口的(唯一的setValue方法抛异常),因此只能用于只读遍历。

    此类的具体代码如下:

        /*
         *Node类是没有提供修改入口的(setValue方法抛异常,供子类实现),
         即是可读的。只能用于只读遍历。
         */
        static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            volatile V val;//volatile,保证可见性
            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; }
            /*
                HashMap中Node类的hashCode()方法中的代码为:Objects.hashCode(key) ^ Objects.hashCode(value)
                而Objects.hashCode(key)最终也是调用了 key.hashCode(),因此,效果一样。写法不一样罢了
            */;
            public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
            public final String toString(){ return key + "=" + val; }
            public final V setValue(V value) { // 不允许修改value值,HashMap允许
                throw new UnsupportedOperationException();
            }
            /*
                 HashMap使用if (o == this),且嵌套if;ConcurrentHashMap使用&& 
                 个人觉得HashMap格式的代码更好阅读和理解
            */
            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.
             *增加find方法辅助get方法  ,HashMap中的Node类中没有此方法
             */
            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;
            }
        }
    

    我们在看这个类时,可以与HashMap中的Node类的具体代码进行比较,发现在具体的实现上,有一定的细微的区别。

    例如:在ConcurrentHashMap.Node的hashCode的代码是这样的:

         public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }

    而HashMap.Node的hashCode的代码是这样的:

        public final int hashCode() {
                    return Objects.hashCode(key) ^ Objects.hashCode(value);
                }

    而Objects.hashCode(key)最终也是调用了 key.hashCode(),因此,两者的效果一样,写法不一样罢了。

    除了hashCode方法有一点差别,Node类中的find方法在两个类的实现中的写法也不一样。

    2、TreeNode

        /*
         * Nodes for use in TreeBins 
         */
        static final class TreeNode<K,V> extends Node<K,V> {
            TreeNode<K,V> parent;  // red-black tree links
            TreeNode<K,V> left;
            TreeNode<K,V> right;
            TreeNode<K,V> prev;    // needed to unlink next upon deletion
            boolean red;
    
            TreeNode(int hash, K key, V val, Node<K,V> next,
                     TreeNode<K,V> parent) {
                super(hash, key, val, next);
                this.parent = parent;
            }
    
            Node<K,V> find(int h, Object k) {
                return findTreeNode(h, k, null);
            }
    
            /*
             * Returns the TreeNode (or null if not found) for the given key
             * starting at given root.
             *根据给定的key值从root节点出发找出节点
             *  
             */
            final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
                if (k != null) {//HashMap没有非空判断
                    TreeNode<K,V> p = this;
                    do  {
                        int ph, dir; K pk; TreeNode<K,V> q;
                        TreeNode<K,V> pl = p.left, pr = p.right;
                        if ((ph = p.hash) > h)
                            p = pl;
                        else if (ph < h)
                            p = pr;
                        else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                            return p;
                        else if (pl == null)
                            p = pr;
                        else if (pr == null)
                            p = pl;
                        else if ((kc != null ||
                                  (kc = comparableClassFor(k)) != null) &&
                                 (dir = compareComparables(kc, k, pk)) != 0)
                            p = (dir < 0) ? pl : pr;
                        else if ((q = pr.findTreeNode(h, k, kc)) != null)
                            return q;
                        else
                            p = pl;
                    } while (p != null);
                }
                return null;
            }
        }

    和HashMap相比,这里的TreeNode相当简洁;ConcurrentHashMap链表转树时,并不会直接转,
    正如注释(Nodes for use in TreeBins)所说,只是把这些节点包装成TreeNode放到TreeBin中,
    再由TreeBin来转化红黑树。红黑树不理解没关系,并不影响看ConcurrentHashMap的内部实现

    3、TreeBins

    TreeBin用于封装维护TreeNode,包含putTreeVal、lookRoot、UNlookRoot、remove、balanceInsetion、balanceDeletion等方法,当链表转树时,用于封装TreeNode,也就是说,ConcurrentHashMap的红黑树存放的时TreeBin,而不是treeNode。

    TreeBins类代码太长,截取部分代码如下:

        static final class TreeBin<K,V> extends Node<K,V> {
            TreeNode<K,V> root;
            volatile TreeNode<K,V> first;
            volatile Thread waiter;
            volatile int lockState;
            // values for lockState
            static final int WRITER = 1; // set while holding write lock
            static final int WAITER = 2; // set when waiting for write lock
            static final int READER = 4; // increment value for setting read lock
    
            /**
             * Creates bin with initial set of nodes headed by b.
             */
            TreeBin(TreeNode<K,V> b) {
                super(TREEBIN, null, null, null);
                this.first = b;
                TreeNode<K,V> r = null;
                for (TreeNode<K,V> x = b, next; x != null; x = next) {
                    next = (TreeNode<K,V>)x.next;
                    x.left = x.right = null;
                    if (r == null) {
                        x.parent = null;
                        x.red = false;
                        r = x;
                    }
                    else {
                        K k = x.key;
                        int h = x.hash;
                        Class<?> kc = null;
                        for (TreeNode<K,V> p = r;;) {
                            int dir, ph;
                            K pk = p.key;
                            if ((ph = p.hash) > h)
                                dir = -1;
                            else if (ph < h)
                                dir = 1;
                            else if ((kc == null &&
                                      (kc = comparableClassFor(k)) == null) ||
                                     (dir = compareComparables(kc, k, pk)) == 0)
                                dir = tieBreakOrder(k, pk);
                                TreeNode<K,V> xp = p;
                            if ((p = (dir <= 0) ? p.left : p.right) == null) {
                                x.parent = xp;
                                if (dir <= 0)
                                    xp.left = x;
                                else
                                    xp.right = x;
                                r = balanceInsertion(r, x);
                                break;
                            }
                        }
                    }
                }
                this.root = r;
                assert checkInvariants(root);
            }
            //........other methods
        }

    5、ForwardingNode:在transfer操作中,将一个节点插入到桶中

        /*
         * A node inserted at head of bins during transfer operations.
         *在transfer操作中,一个节点插入到bins中
         */
        static final class ForwardingNode<K,V> extends Node<K,V> {
            final Node<K,V>[] nextTable;
            ForwardingNode(Node<K,V>[] tab) {
                //Node(int hash, K key, V val, Node<K,V> next)是Node类的构造函数
                super(MOVED, null, null, null);
                this.nextTable = tab;
            }
    
            Node<K,V> find(int h, Object k) {
                // loop to avoid arbitrarily deep recursion on forwarding nodes
                outer: for (Node<K,V>[] tab = nextTable;;) {
                    Node<K,V> e; int n;
                    if (k == null || tab == null || (n = tab.length) == 0 ||
                        (e = tabAt(tab, (n - 1) & h)) == null)
                        return null;
                    for (;;) {
                        int eh; K ek;
                        if ((eh = e.hash) == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        if (eh < 0) {
                            if (e instanceof ForwardingNode) {
                                tab = ((ForwardingNode<K,V>)e).nextTable;
                                continue outer;
                            }
                            else
                                return e.find(h, k);
                        }
                        if ((e = e.next) == null)
                            return null;
                    }
                }
            }
        }

    ConcurrentHashMap类中的put(K key, V value)方法的原理分析

    我们对Node、TreeNode、TreeBin有一点认识后,我们就可以看下ConcurrentHashMap类的put方法是如何来实现的了,这里给出一个建议,关于容器我们用的最多的就是put、get方法了,我们看源码的实现,我们核心要关注的就是put、get方法的实现,只要我们弄懂这两个方法实现,这个类的大概实现思想我们也就知道了哈

    基于此,我们就先来看ConcurrentHashMap类的put方法

    put(K key, V value)方法的功能:将制定的键值对映射到table中,key/value均不能为null

    put方法的代码如下:

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

    由于直接是调用了putVal(key, value, false)方法,那就我们就继续看。

    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();
            int hash = spread(key.hashCode());//计算hash值,两次hash操作
            int binCount = 0;
            for (Node<K,V>[] tab = table;;) {//类似于while(true),死循环,直到插入成功 
                Node<K,V> f; int n, i, fh;
                if (tab == null || (n = tab.length) == 0)//检查是否初始化了,如果没有,则初始化
                    tab = initTable();
                    /*
                        i=(n-1)&hash 等价于i=hash%n(前提是n为2的幂次方).即取出table中位置的节点用f表示。
                        有如下两种情况:
                        1、如果table[i]==null(即该位置的节点为空,没有发生碰撞),则利用CAS操作直接存储在该位置,
                            如果CAS操作成功则退出死循环。
                        2、如果table[i]!=null(即该位置已经有其它节点,发生碰撞)
                    */
                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)//检查table[i]的节点的hash是否等于MOVED,如果等于,则检测到正在扩容,则帮助其扩容
                    tab = helpTransfer(tab, f);//帮助其扩容
                else {//运行到这里,说明table[i]的节点的hash值不等于MOVED。
                    V oldVal = null;
                    synchronized (f) {//锁定,(hash值相同的链表的头节点)
                        if (tabAt(tab, i) == f) {//避免多线程,需要重新检查
                            if (fh >= 0) {//链表节点
                                binCount = 1;
                                /*
                                下面的代码就是先查找链表中是否出现了此key,如果出现,则更新value,并跳出循环,
                                否则将节点加入到里阿尼报末尾并跳出循环
                                */
                                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)//仅putIfAbsent()方法中onlyIfAbsent为true
                                            e.val = value;//putIfAbsent()包含key则返回get,否则put并返回  
                                        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)//实则是>8,执行else,说明该桶位本就有Node
                            treeifyBin(tab, i);//若length<64,直接tryPresize,两倍table.length;不转树 
                        if (oldVal != null)
                            return oldVal;
                        break;
                    }
                }
            }
            addCount(1L, binCount);
            return null;
        }

    代码比较长哈,但是不要怕,我刚开始看的时候,也被长度给吓住了,怎么可以有这么长的方法呢,HashMap中put方法的长度就很短的么。

    虽然很长,但是思路相当的简单。代码详细流程如下,在上面代码中也有详细的注释

    /*
    putVal(K key, V value, boolean onlyIfAbsent)方法干的工作如下:
    1、检查key/value是否为空,如果为空,则抛异常,否则进行2
    2、进入for死循环,进行3
    3、检查table是否初始化了,如果没有,则调用initTable()进行初始化然后进行 2,否则进行4
    4、根据key的hash值计算出其应该在table中储存的位置i,取出table[i]的节点用f表示。
        根据f的不同有如下三种情况:1)如果table[i]==null(即该位置的节点为空,没有发生碰撞),
                                    则利用CAS操作直接存储在该位置,如果CAS操作成功则退出死循环。
                                    2)如果table[i]!=null(即该位置已经有其它节点,发生碰撞),碰撞处理也有两种情况
                                        2.1)检查table[i]的节点的hash是否等于MOVED,如果等于,则检测到正在扩容,则帮助其扩容
                                        2.2)说明table[i]的节点的hash值不等于MOVED,如果table[i]为链表节点,则将此节点插入链表中即可
                                            如果table[i]为树节点,则将此节点插入树中即可。插入成功后,进行 5
    5、如果table[i]的节点是链表节点,则检查table的第i个位置的链表是否需要转化为数,如果需要则调用treeifyBin函数进行转化
    
    
    */
    

    可能你觉得上面的详细流程也比较多哈,但是不要怕,用两句话来总结的话,是如下的两步:

    1、第一步根据给定的key的hash值找到其在table中的位置index。

    2、找到位置index后,存储进行就好了。

    只是这里的存储有三种情况罢了,第一种:table[index]中没有任何其他元素,即此元素没有发生碰撞,这种情况直接存储就好了哈。第二种,table[i]存储的是一个链表,如果链表不存在key则直接加入到链表尾部即可,如果存在key则更新其对应的value。第三种,table[i]存储的是一个树,则按照树添加节点的方法添加就好。

    在putVal函数,出现了如下几个函数

    1、casTabAt tabAt 等CAS操作

    2、initTable 作用是初始化table数组

    3、treeifyBin 作用是将table[i]的链表转化为树

    下面将分别进行介绍。

    这里给出第二个建议,当一个类的代码量相当大且复杂时,从我们感兴趣的方法出发,然后是遇到哪个方法就才解决哪个方法

    3个用的比较多的CAS操作:casTabAt tabAt setTabAt

        /*
            3个用的比较多的CAS操作
        */
    
        @SuppressWarnings("unchecked") // ASHIFT等均为private static final  
        static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { // 获取索引i处Node  
            return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);  
        }  
        // 利用CAS算法设置i位置上的Node节点(将c和table[i]比较,相同则插入v)。  
        static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,  
                                            Node<K,V> c, Node<K,V> v) {  
            return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);  
        }  
        // 设置节点位置的值,仅在上锁区被调用  
        static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {  
            U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);  
        }

    initTable() terrifyBin方法

    在putVal方法中遇到的第一个扩容函数为:initTable,即初始化

    代码如下,注释相当详细,这里就不再解释。

        /**
         * Initializes table, using the size recorded in sizeCtl.
         */
        private final Node<K,V>[] initTable() {
            Node<K,V>[] tab; int sc;
            while ((tab = table) == null || tab.length == 0) {
                if ((sc = sizeCtl) < 0)//如果sizeCtl为负数,则说明已经有其它线程正在进行扩容,即正在初始化或初始化完成
                    Thread.yield(); // lost initialization race; just spin
                    //如果CAS成功,则表示正在初始化,设置为 -1,否则说明其它线程已经对其正在初始化或是已经初始化完毕
                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);//即sc = 0.75n。
                        }
                    } finally {
                        sizeCtl = sc;//sizeCtl = 0.75*Capacity,为扩容门限
                    }
                    break;
                }
            }
            return tab;
        }
    

    treeifyBin方法:将数组tab的第index位置的链表转化为 树

        /*
         *链表转树:将将数组tab的第index位置的链表转化为 树
         */
        private final void treeifyBin(Node<K,V>[] tab, int index) {
            Node<K,V> b; int n, sc;
            if (tab != null) {
                if ((n = tab.length) < MIN_TREEIFY_CAPACITY)// 容量<64,则table两倍扩容,不转树了
                    tryPresize(n << 1);
                else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
                    synchronized (b) { // 读写锁  
                        if (tabAt(tab, index) == b) {
                            TreeNode<K,V> hd = null, tl = null;
                            for (Node<K,V> e = b; e != null; e = e.next) {
                                TreeNode<K,V> p =
                                    new TreeNode<K,V>(e.hash, e.key, e.val,
                                                      null, null);
                                if ((p.prev = tl) == null)
                                    hd = p;
                                else
                                    tl.next = p;
                                tl = p;
                            }
                            setTabAt(tab, index, new TreeBin<K,V>(hd));
                        }
                    }
                }
            }
        }

    treeifyBin方法的思想也相当的简单,如下:

    1、检查下table的长度是否大于等于MIN_TREEIFY_CAPACITY(64),如果不大于,则调用tryPresize方法将table两倍扩容就可以了,就不降链表转化为树了。如果大于,则就将table[i]的链表转化为树。

    tryPresize方法

    在putVal方法中遇到的第二个扩容函数为:tryPresize

        /*
            扩容相关
            tryPresize在putAll以及treeifyBin中调用
        */
        private final void tryPresize(int size) {
            // 给定的容量若>=MAXIMUM_CAPACITY的一半,直接扩容到允许的最大值,否则调用tableSizeFor函数扩容 
            int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
                tableSizeFor(size + (size >>> 1) + 1);//tableSizeFor(count)的作用是找到大于等于count的最小的2的幂次方
            int sc;
            while ((sc = sizeCtl) >= 0) {//只有大于等于0才表示该线程可以扩容,具体看sizeCtl的含义
                Node<K,V>[] tab = table; int n;
                if (tab == null || (n = tab.length) == 0) {//没有被初始化
                    n = (sc > c) ? sc : c;
                    // 期间没有其他线程对表操作,则CAS将SIZECTL状态置为-1,表示正在进行初始化  
                    if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                        try {
                            if (table == tab) {//再一次检查
                                @SuppressWarnings("unchecked")
                                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                                table = nt;
                                sc = n - (n >>> 2);//无符号右移2位,此即0.75*n 
                            }
                        } finally {
                            sizeCtl = sc;// 更新扩容阀值  
                        }
                    }
                }
                // 若欲扩容值不大于原阀值,或现有容量>=最值,什么都不用做了 
                else if (c <= sc || n >= MAXIMUM_CAPACITY)
                    break;
                else if (tab == table) { // table不为空,且在此期间其他线程未修改table  
                    int rs = resizeStamp(n);
                    if (sc < 0) {//这里的sc可能小于零么???不明白为什么会有此判断
                        Node<K,V>[] nt;//RESIZE_STAMP_SHIFT=16,MAX_RESIZERS=2^15-1  
                        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);
                }
            }
        }
    
        /*
          Returns the stamp bits for resizing a table of size n.当扩容到n时,调用该函数返回一个标志位
          Must be negative when shifted left by RESIZE_STAMP_SHIFT.
          numberOfLeadingZeros返回n对应32位二进制数左侧0的个数,如9(1001)返回28  
          RESIZE_STAMP_BITS=16,
          因此返回值为:(参数n的左侧0的个数)|(2^15)  
         */
        static final int resizeStamp(int n) {
            return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
        }

    既然是扩容,思路就比较简单哈,注释的相当详细,就不介绍了哈,在这个函数中调用transfer函数,transfer方法的代码太长,这里不贴出。

    在transfer方法中,用到了如下的属性

    private transient volatile Node<K,V>[] nextTable; 

    仅仅在扩容使用,并且此时非空。

    在扩容的过程中,还有一个辅助方法:helpTransfer方法。

    代码如下:

        /*
         * Helps transfer if a resize is in progress.
         *在多线程情况下,如果发现其它线程正在扩容,则帮助转移元素。
         (只有这种情况会被调用)从某种程度上说,其“优先级”很高,只要检测到扩容,就会放下其他工作,先扩容。
         */
        final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {// 调用之前,nextTable一定已存在。
            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;
        }

    以上就把跟putVal相关的函数都看了一篇哈,可能细节我们没有看懂,但是各个方法的思路我们都清楚了,继续往下面来看

    分析ConcurrentHashMap类的get(int key)方法

    看完了ConcurrentHashMap类的put(int key ,int value)方法的内部实现,接着看此类的get(int key)方法。

        /*
         功能:根据key在Map中找出其对应的value,如果不存在key,则返回null,
         其中key不允许为null,否则抛异常
         */
        public V get(Object key) {
            Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
            int h = spread(key.hashCode());//两次hash计算出hash值
            if ((tab = table) != null && (n = tab.length) > 0 &&//table不能为null,是吧
                (e = tabAt(tab, (n - 1) & h)) != null) {//table[i]不能为空,是吧
                if ((eh = e.hash) == h) {//检查头结点
                    if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                        return e.val;
                }
                else if (eh < 0)//table[i]为一颗树
                    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;
        }
    

    get(int key)方法代码实现流程如下:

    1、根据key调用spread计算hash值;并根据计算出来的hash值计算出该key在table出现的位置i.

    2、检查table是否为空;如果为空,返回null,否则进行3

    3、检查table[i]处桶位不为空;如果为空,则返回null,否则进行4

    4、先检查table[i]的头结点的key是否满足条件,是则返回头结点的value;否则分别根据树、链表查询。

    get方法的思想是不是也很简单哈,与HashMap的get方法一模一样,分析到这里,ConcurrentHashMap类的源码的大概实现思路我们就基本清晰了哈,本着学习的精神,我们还是稍微看下其他的方法哈,例如:containsKey、remove、size等等

    分析ConcurrentHashMap类的containsKey/containsValue方法

    看下containsKey/containsValue方法

        /*
         * Tests if the specified object is a key in this table.
         */
        public boolean containsKey(Object key) {
            return get(key) != null;//直接调用get(int key)方法即可,如果有返回值,则说明是包含key的
        }
    
        /*
         *功能,检查在所有映射(k,v)中只要出现一次及以上的v==value,返回true
         *注意:这个方法可能需要一个完全遍历Map,因此比containsKey要慢的多
         */
        public boolean containsValue(Object value) {
            if (value == null)
                throw new NullPointerException();
            Node<K,V>[] t;
            if ((t = table) != null) {
                Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
                for (Node<K,V> p; (p = it.advance()) != null; ) {
                    V v;
                    if ((v = p.val) == value || (v != null && value.equals(v)))
                        return true;
                }
            }
            return false;
        }
    

    containsKey/containsValue方法的内部实现也比较简单哈。这里也不再详细介绍。

    分析ConcurrentHashMap类的size()方法

        // Original (since JDK1.2) Map methods
        public int size() {// 旧版本方法,和推荐的mappingCount返回的值基本无区别
            long n = sumCount();
            return ((n < 0L) ? 0 :
                    (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                    (int)n);
        }
    

    这个方法是从JDK1.2版本开始就有的方法了。而ConcurrentHashMap在JDK1.8版本中还提供了另外一种方法可以获取大小,这个方法就是mappingCount。

    代码如下:

        // ConcurrentHashMap-only methods
    
        /**
         * Returns the number of mappings. This method should be used
         * instead of {@link #size} because a ConcurrentHashMap may
         * contain more mappings than can be represented as an int. The
         * value returned is an estimate(估计); the actual count may differ if
         * there are concurrent insertions or removals.
         *
         * @return the number of mappings
         * @since 1.8
         */
        public long mappingCount() {
            long n = sumCount();
            return (n < 0L) ? 0L : n; // ignore transient negative values
        }
    

    根据mappingCount()方法头上的注释,我们可以得到如下的信息:

    1、这个应该用来代替size()方法被使用。这是因为ConcurrentHashMap可能比包含更多的映射结果,即超过int类型的最大值。

    2、这个方法返回值是一个估计值,由于存在并发的插入和删除,因此返回值可能与实际值会有出入。

    虽然注释这么才说使用mappingCount来代替size()方法,但是我们比较两个方法的源码你会发现这两个方法的源码基本一致。

    在size()方法和mappingCount方法中都出现了sumCount()方法,因此,我们也顺便看一下。

        /* ---------------- Counter support -------------- */
    
        /**
         * A padded cell for distributing counts.  Adapted from LongAdder
         * and Striped64.  See their internal docs for explanation.
         */
        @sun.misc.Contended static final class CounterCell {
            volatile long value;
            CounterCell(long x) { value = x; }
        }
        // Table of counter cells. When non-null, size is a power of 2
        private transient volatile CounterCell[] counterCells;
        //ConcurrentHashMap中元素个数,基于CAS无锁更新,但返回的不一定是当前Map的真实元素个数。
        private transient volatile long baseCount; 
    
        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;
        }

    最后看下,clear,remove方法

    remove方法的代码如下;

        /*
         * Removes the key (and its corresponding value) from this map.
         * This method does nothing if the key is not in the map.
         */
        public V remove(Object key) {
            return replaceNode(key, null, null);
        }
    
        /*
         *如果Map中存在(key,value)节点,则用对象cd来代替,
         *如果value为空,则删除此节点。
         */
        final V replaceNode(Object key, V value, Object cv) {
            int hash = spread(key.hashCode());//计算hash值
            for (Node<K,V>[] tab = table;;) {//死循环,直到找到
                Node<K,V> f; int n, i, fh;
                if (tab == null || (n = tab.length) == 0 ||
                    (f = tabAt(tab, i = (n - 1) & hash)) == null)//如果为空,则立即返回
                    break;
                else if ((fh = f.hash) == MOVED)//如果检测到其它线程正在扩容,则先帮助扩容,然后再来寻找,可见扩容的优先级之高
                    tab = helpTransfer(tab, f);
                else {
                    V oldVal = null;
                    boolean validated = false;
                    synchronized (f) {  //开始锁住这个桶,然后进行比对寻找满足(key,value)的节点
                        if (tabAt(tab, i) == f) { //重新检查,避免由于多线程的原因table[i]已经被修改
                            if (fh >= 0) {//链表节点
                                validated = true;
                                for (Node<K,V> e = f, pred = null;;) {
                                    K ek;
                                    if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                         (ek != null && key.equals(ek)))) {//满足条件就是找到key出现的节点位置
                                        V ev = e.val;
                                        if (cv == null || cv == ev ||
                                            (ev != null && cv.equals(ev))) {
                                            oldVal = ev;
                                            if (value != null)//value不为空,则更新值
                                                e.val = value;
                                            //value为空,则删除此节点
                                            else if (pred != null)
                                                pred.next = e.next;
                                            else
                                                setTabAt(tab, i, e.next);//符合条件的节点e为头结点的情况
                                        }
                                        break;
                                    }
                                    //更改指向,继续向后循环
                                    pred = e;
                                    if ((e = e.next) == null)//如果为到链表末尾了,则直接退出即可
                                        break;
                                }
                            }
                            else if (f instanceof TreeBin) {//树节点
                                validated = true;
                                TreeBin<K,V> t = (TreeBin<K,V>)f;
                                TreeNode<K,V> r, p;
                                if ((r = t.root) != null &&
                                    (p = r.findTreeNode(hash, key, null)) != null) {
                                    V pv = p.val;
                                    if (cv == null || cv == pv ||
                                        (pv != null && cv.equals(pv))) {
                                        oldVal = pv;
                                        if (value != null)
                                            p.val = value;
                                        else if (t.removeTreeNode(p))
                                            setTabAt(tab, i, untreeify(t.first));
                                    }
                                }
                            }
                        }
                    }
                    if (validated) {
                        if (oldVal != null) {
                            if (value == null)//如果删除了节点,则要减1
                                addCount(-1L, -1);
                            return oldVal;
                        }
                        break;
                    }
                }
            }
            return null;
        }

    remove方法的实现思路也比较简单。如下;

    1、先根据key的hash值计算书其在table的位置 i。

    2、检查table[i]是否为空,如果为空,则返回null,否则进行3

    3、在table[i]存储的链表(或树)中开始遍历比对寻找,如果找到节点符合key的,则判断value是否为null来决定是否是更新oldValue还是删除该节点。

    clear()方法的源码如下,这里就不再进行分析了哈。

        /**
         * Removes all of the mappings from this map.
         */
        public void clear() {
            long delta = 0L; // negative number of deletions
            int i = 0;
            Node<K,V>[] tab = table;
            while (tab != null && i < tab.length) {
                int fh;
                Node<K,V> f = tabAt(tab, i);
                if (f == null)
                    ++i;
                else if ((fh = f.hash) == MOVED) {
                    tab = helpTransfer(tab, f);
                    i = 0; // restart
                }
                else {
                    synchronized (f) {
                        if (tabAt(tab, i) == f) {
                            Node<K,V> p = (fh >= 0 ? f :
                                           (f instanceof TreeBin) ?
                                           ((TreeBin<K,V>)f).first : null);
                            while (p != null) {
                                --delta;
                                p = p.next;
                            }
                            setTabAt(tab, i++, null);
                        }
                    }
                }
            }
            if (delta != 0L)
                addCount(delta, -1);
        }

    小结

    以上就是关于ConcurrentHashMap的全部介绍,是不是比较简单哈。话虽这么说,但是还是需要我们花时间和精力来慢慢看和分析总结,这样我们才会有收获,本篇博文对链表和数的转化并没有过多的介绍,以及关于在树中插入节点和查找节点也没有过多的介绍哈

    展开全文
  • java源码包---java 源码 大量 实例

    千次下载 热门讨论 2013-04-18 23:15:26
    内容索引:JAVA源码,媒体网络,飞鸽传  Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很...
  • java源码分析之ArrayList

    万次阅读 多人点赞 2013-01-25 08:52:19
     这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。   增加...

     ArrayList就是传说中的动态数组,就是Array的复杂版本,它提供了如下一些好处:动态的增加和减少元素、灵活的设置数组的大小......

        认真阅读本文,我相信一定会对你有帮助。比如为什么ArrayList里面提供了一个受保护的removeRange方法?提供了其他没有被调用过的私有方法?

        首先看到对ArrayList的定义:

    public class ArrayList<E> extends AbstractList<E>  implements List<E>, RandomAccess, Cloneable, java.io.Serializable

     从ArrayList<E>可以看出它是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。

        AbstractList提供了List接口的默认实现(个别方法为抽象方法)。

        List接口定义了列表必须实现的方法。

        RandomAccess是一个标记接口,接口内没有定义任何内容。

        实现了Cloneable接口的类,可以调用Object.clone方法返回该对象的浅拷贝。

        通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。

        ArrayList的属性

        ArrayList定义只定义类两个私有属性:

    /**
          * The array buffer into which the elements of the ArrayList are stored.
          * The capacity of the ArrayList is the length of this array buffer.
          */
         private transient Object[] elementData;
     
         /**
          * The size of the ArrayList (the number of elements it contains).
          *
          * @serial
          */
         private int size;

      很容易理解,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
    
        有个关键字需要解释:transient。
    
        Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
    transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。
    
        有点抽象,看个例子应该能明白。

    public class UserInfo implements Serializable {
         private static final long serialVersionUID = 996890129747019948L;
         private String name;
         private transient String psw;
     
         public UserInfo(String name, String psw) {
             this.name = name;
             this.psw = psw;
         }
     
         public String toString() {
             return "name=" + name + ", psw=" + psw;
         }
     }
     
     public class TestTransient {
         public static void main(String[] args) {
             UserInfo userInfo = new UserInfo("张三", "123456");
             System.out.println(userInfo);
             try {
                 // 序列化,被设置为transient的属性没有被序列化
                 ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
                         "UserInfo.out"));
                 o.writeObject(userInfo);
                 o.close();
             } catch (Exception e) {
                 // TODO: handle exception
                 e.printStackTrace();
             }
             try {
                 // 重新读取内容
                 ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                         "UserInfo.out"));
                 UserInfo readUserInfo = (UserInfo) in.readObject();
                 //读取后psw的内容为null
                 System.out.println(readUserInfo.toString());
             } catch (Exception e) {
                 // TODO: handle exception
                 e.printStackTrace();
             }
         }
     }

     被标记为transient的属性在对象被序列化的时候不会被保存。

        接着回到ArrayList的分析中......

        ArrayList的构造方法

        看完属性看构造方法。ArrayList提供了三个构造方法:

    /**
          * Constructs an empty list with the specified initial capacity.
          */
         public ArrayList(int initialCapacity) {
         super();
             if (initialCapacity < 0)
                 throw new IllegalArgumentException("Illegal Capacity: "+
                                                    initialCapacity);
         this.elementData = new Object[initialCapacity];
         }
     
         /**
          * Constructs an empty list with an initial capacity of ten.
          */
         public ArrayList() {
         this(10);
         }
     
         /**
          * Constructs a list containing the elements of the specified
          * collection, in the order they are returned by the collection's
          * iterator.
          */
         public ArrayList(Collection<? extends E> c) {
         elementData = c.toArray();
         size = elementData.length;
         // c.toArray might (incorrectly) not return Object[] (see 6260652)
         if (elementData.getClass() != Object[].class)
             elementData = Arrays.copyOf(elementData, size, Object[].class);
         }

       第一个构造方法使用提供的initialCapacity来初始化elementData数组的大小。第二个构造方法调用第一个构造方法并传入参数10,即默认elementData数组的大小为10。第三个构造方法则将提供的集合转成数组返回给elementData(返回若不是Object[]将调用Arrays.copyOf方法将其转为Object[])。

        ArrayList的其他方法

        add(E e)

        add(E e)都知道是在尾部添加一个元素,如何实现的呢?

    public boolean add(E e) {
        ensureCapacity(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
        }

     书上都说ArrayList是基于数组实现的,属性中也看到了数组,具体是怎么实现的呢?比如就这个添加元素的方法,如果数组大,则在将某个位置的值设置为指定元素即可,如果数组容量不够了呢?

        看到add(E e)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增。例如初次添加时,size为0,add将elementData[0]赋值为e,然后size设置为1(类似执行以下两条语句elementData[0]=e;size=1)。将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?这里关键就在ensureCapacity(size+1)中了。

        根据ensureCapacity的方法名可以知道是确保容量用的。ensureCapacity(size+1)后面的注释可以明白是增加modCount的值(加了俩感叹号,应该蛮重要的,来看看)。

    /**
          * Increases the capacity of this <tt>ArrayList</tt> instance, if
          * necessary, to ensure that it can hold at least the number of elements
          * specified by the minimum capacity argument.
          *
          * @param   minCapacity   the desired minimum capacity
          */
         public void ensureCapacity(int minCapacity) {
         modCount++;
         int oldCapacity = elementData.length;
         if (minCapacity > oldCapacity) {
             Object oldData[] = elementData;
             int newCapacity = (oldCapacity * 3)/2 + 1;
                 if (newCapacity < minCapacity)
             newCapacity = minCapacity;
                 // minCapacity is usually close to size, so this is a win:
                 elementData = Arrays.copyOf(elementData, newCapacity);
         }
         }

    The number of times this list has been structurally modified.

        这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。

        增加modCount之后,判断minCapacity(即size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为max((oldCapacity*3)/2+1,minCapacity),调整elementData容量为新的容量,即将返回一个内容为原数组元素,大小为新容量的数组赋给elementData;否则不做操作。

        所以调用ensureCapacity至少将elementData的容量增加的1,所以elementData[size]不会出现越界的情况。

        容量的拓展将导致数组元素的复制,多次拓展容量将执行多次整个数组内容的复制。若提前能大致判断list的长度,调用ensureCapacity调整容量,将有效的提高运行速度。

        可以理解提前分配好空间可以提高运行速度,但是测试发现提高的并不是很大,而且若list原本数据量就不会很大效果将更不明显。

        add(int index, E element)

        add(int index,E element)在指定位置插入元素。

    public void add(int index, E element) {
         if (index > size || index < 0)
             throw new IndexOutOfBoundsException(
             "Index: "+index+", Size: "+size);
     
         ensureCapacity(size+1);  // Increments modCount!!
         System.arraycopy(elementData, index, elementData, index + 1,
                  size - index);
         elementData[index] = element;
         size++;
         }

       首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置),然后将index位置的值指向element。       

        addAll(Collection<? extends E> c)

    public boolean addAll(Collection<? extends E> c) {
         Object[] a = c.toArray();
             int numNew = a.length;
         ensureCapacity(size + numNew);  // Increments modCount
             System.arraycopy(a, 0, elementData, size, numNew);
             size += numNew;
         return numNew != 0;
         }

      先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到elementData的尾部,调整size。根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。

        addAll(int index,Collection<? extends E> c)

    public boolean addAll(int index, Collection<? extends E> c) {
         if (index > size || index < 0)
             throw new IndexOutOfBoundsException(
             "Index: " + index + ", Size: " + size);
     
         Object[] a = c.toArray();
         int numNew = a.length;
         ensureCapacity(size + numNew);  // Increments modCount
     
         int numMoved = size - index;
         if (numMoved > 0)
             System.arraycopy(elementData, index, elementData, index + numNew,
                      numMoved);
     
             System.arraycopy(a, 0, elementData, index, numNew);
         size += numNew;
         return numNew != 0;
         }

      先判断index是否越界。其他内容与addAll(Collection<? extends E> c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到elementData的index位置至index+X。

        clear()

    public void clear() {
         modCount++;
     
         // Let gc do its work
         for (int i = 0; i < size; i++)
             elementData[i] = null;
     
         size = 0;
         }

    clear的时候并没有修改elementData的长度(好不容易申请、拓展来的,凭什么释放,留着搞不好还有用呢。这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间),只是将所有元素置为null,size设置为0。

        clone()

        返回此 ArrayList 实例的浅表副本。(不复制这些元素本身。)

    public Object clone() {
         try {
             ArrayList<E> v = (ArrayList<E>) super.clone();
             v.elementData = Arrays.copyOf(elementData, size);
             v.modCount = 0;
             return v;
         } catch (CloneNotSupportedException e) {
             // this shouldn't happen, since we are Cloneable
             throw new InternalError();
         }
         }

      调用父类的clone方法返回一个对象的副本,将返回对象的elementData数组的内容赋值为原对象elementData数组的内容,将副本的modCount设置为0。

        contains(Object)

    public boolean contains(Object o) {
         return indexOf(o) >= 0;
         }

     indexOf方法返回值与0比较来判断对象是否在list中。接着看indexOf。

        indexOf(Object)

    public int indexOf(Object o) {
         if (o == null) {
             for (int i = 0; i < size; i++)
             if (elementData[i]==null)
                 return i;
         } else {
             for (int i = 0; i < size; i++)
             if (o.equals(elementData[i]))
                 return i;
         }
         return -1;
         }

    通过遍历elementData数组来判断对象是否在list中,若存在,返回index([0,size-1]),若不存在则返回-1。所以contains方法可以通过indexOf(Object)方法的返回值来判断对象是否被包含在list中。

        既然看了indexOf(Object)方法,接着就看lastIndexOf,光看名字应该就明白了返回的是传入对象在elementData数组中最后出现的index值。

    public int lastIndexOf(Object o) {
         if (o == null) {
             for (int i = size-1; i >= 0; i--)
             if (elementData[i]==null)
                 return i;
         } else {
             for (int i = size-1; i >= 0; i--)
             if (o.equals(elementData[i]))
                 return i;
         }
         return -1;
         }

    采用了从后向前遍历element数组,若遇到Object则返回index值,若没有遇到,返回-1。

        get(int index)

        这个方法看着很简单,应该是返回elementData[index]就完了。

    public E get(int index) {
         RangeCheck(index);
    
         return (E) elementData[index];
         }

    但看代码的时候看到调用了RangeCheck方法,而且还是大写的方法,看看究竟有什么内容吧。

    /**
          * Checks if the given index is in range.
     */
     private void RangeCheck(int index) {
         if (index >= size)
             throw new IndexOutOfBoundsException(
             "Index: "+index+", Size: "+size);
         }

     就是检查一下是不是超出数组界限了,超出了就抛出IndexOutBoundsException异常。为什么要大写呢???

        isEmpty()

        直接返回size是否等于0。

        remove(int index)

    public E remove(int index) {
         RangeCheck(index);
         modCount++;
         E oldValue = (E) elementData[index];
         int numMoved = size - index - 1;
         if (numMoved > 0)
             System.arraycopy(elementData, index+1, elementData, index,
                      numMoved);
         elementData[--size] = null; // Let gc do its work
         return oldValue;
         }

    首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。

        remove(Object o)

    public boolean remove(Object o) {
         if (o == null) {
                 for (int index = 0; index < size; index++)
             if (elementData[index] == null) {
                 fastRemove(index);
                 return true;
             }
         } else {
             for (int index = 0; index < size; index++)
             if (o.equals(elementData[index])) {
                 fastRemove(index);
                 return true;
             }
             }
         return false;
         }

      首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。下面是fastRemove的代码,基本和remove(index)一致。

    private void fastRemove(int index) {
             modCount++;
             int numMoved = size - index - 1;
             if (numMoved > 0)
                 System.arraycopy(elementData, index+1, elementData, index,
                                  numMoved);
             elementData[--size] = null; // Let gc do its work
         }

      removeRange(int fromIndex,int toIndex)

    protected void removeRange(int fromIndex, int toIndex) {
         modCount++;
         int numMoved = size - toIndex;
             System.arraycopy(elementData, toIndex, elementData, fromIndex,
                              numMoved);
     
         // Let gc do its work
         int newSize = size - (toIndex-fromIndex);
         while (size != newSize)
             elementData[--size] = null;
         }

    执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。

        这个方法是protected,及受保护的方法,为什么这个方法被定义为protected呢?

        这是一个解释,但是可能不容易看明白。http://stackoverflow.com/questions/2289183/why-is-javas-abstractlists-removerange-method-protected

        先看下面这个例子

    ArrayList<Integer> ints = new ArrayList<Integer>(Arrays.asList(0, 1, 2,
                     3, 4, 5, 6));
             // fromIndex low endpoint (inclusive) of the subList
             // toIndex high endpoint (exclusive) of the subList
            ints.subList(2, 4).clear();
             System.out.println(ints);
     输出结果是[0, 1, 4, 5, 6],结果是不是像调用了removeRange(int fromIndex,int toIndex)!哈哈哈,就是这样的。但是为什么效果相同呢?是不是调用了removeRange(int fromIndex,int toIndex)呢?

    set(int index,E element)

    public E set(int index, E element) {
         RangeCheck(index);
     
         E oldValue = (E) elementData[index];
         elementData[index] = element;
         return oldValue;
         }

      首先检查范围,用新元素替换旧元素并返回旧元素。

        size()

        size()方法直接返回size。

        toArray()

    public Object[] toArray() {
             return Arrays.copyOf(elementData, size);
         }

     调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

        toArray(T[] a)

    public <T> T[] toArray(T[] a) {
             if (a.length < size)
                 // Make a new array of a's runtime type, but my contents:
                 return (T[]) Arrays.copyOf(elementData, size, a.getClass());
         System.arraycopy(elementData, 0, a, 0, size);
             if (a.length > size)
                 a[size] = null;
             return a;
         }

     如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

        trimToSize()

    public void trimToSize() {
         modCount++;
         int oldCapacity = elementData.length;
         if (size < oldCapacity) {
                 elementData = Arrays.copyOf(elementData, size);
         }
         }

    由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空间。

         学习Java最好的方式还必须是读源码。读完源码你才会发现这东西为什么是这么玩的,有哪些限制,关键点在哪里等等。而且这些源码都是大牛们写的,你能从中学习到很多。 




    展开全文
  • Java源码分析》:ReferenceQueue、Reference及其子类在看完WeakHashMap源码之后,看了有关于讲解WeakHashMap的些许博客,发现了几个比较有意思的类:Reference、Reference子类(SoftReference、WeakReference、...

    《Java源码分析》:ReferenceQueue、Reference及其子类

    在看完WeakHashMap源码之后,看了有关于讲解WeakHashMap的些许博客,发现了几个比较有意思的类:Reference、Reference子类(SoftReference、WeakReference、PhantomReference)以及ReferenceQueue。

    以前自己只是知道这些类的存在,在看WeakHashMap源码之前并不知道他们的用途,因此,借此机会自己就想对这几个类了解下,查了网上相关资料、看了源码和相关API文档,都没能完全的理解这些类以及这些类和垃圾回收之间的交互,只是有一个小小的认识。下面就来一一进行说明,若有错误,请指出并谅解。

    1、ReferenceQueue类

    由于Reference类中有关于ReferenceQueue的引用,因此,先对ReferenceQueue进行介绍。

    源码中对该类的说明摘入如下:

    Reference queues, to which registered reference objects are appended by the
    garbage collector after the appropriate reachability changes are detected.

    中文意思为:Reference queues,在适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。

    此类中的方法比较少,只有:enqueue(Reference

        //出队标识
        static ReferenceQueue<Object> NULL = new Null<>();
        //出队标识
        static ReferenceQueue<Object> ENQUEUED = new Null<>();
        //锁对象
        static private class Lock { };
        private Lock lock = new Lock();
        //链表的头结点
        private volatile Reference<? extends T> head = null;
        //队列的大小
        private long queueLength = 0;
    
        boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
            synchronized (lock) {
                // Check that since getting the lock this reference hasn't already been
                // enqueued (and even then removed)
                ReferenceQueue<?> queue = r.queue;
                if ((queue == NULL) || (queue == ENQUEUED)) {
                    return false;
                }
                assert queue == this;
                r.queue = ENQUEUED;//入队标识
                r.next = (head == null) ? r : head;
                head = r;
                queueLength++;
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(1);
                }
                lock.notifyAll();
                return true;
            }
        }
    

    2、poll()方法

    实现思路也相当简单,就是判断队列中是否为空,如果不为空,则取出链表中head位置的元素即可,出队的Reference对象要加上出队标识NULL。

    源码如下:

        public Reference<? extends T> poll() {
            if (head == null)
                return null;
            synchronized (lock) {
                return reallyPoll();
            }
        }
    
        @SuppressWarnings("unchecked")
        private Reference<? extends T> reallyPoll() {       /* Must hold lock */
            Reference<? extends T> r = head;
            if (r != null) {
                head = (r.next == r) ?
                    null :
                    r.next; // Unchecked due to the next field having a raw type in Reference
                r.queue = NULL;//出队标识
                r.next = r;//出队的Reference对象的next指向自己
                queueLength--;
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(-1);
                }
                return r;
            }
            return null;
        }

    remove方法这里就不再介绍,以上就是关于ReferenceQueue的一个简单的介绍。

    2.Reference类

    2.1、介绍

    在Reference类源码的开头对此类有一个说明,摘入如下:

    Abstract base class for reference objects. This class defines the
    operations common to all reference objects. Because reference objects are
    implemented in close cooperation with the garbage collector, this class may
    not be subclassed directly.

    比较好理解,中文翻译为:这是引用对象的抽象基类,这个类中定义了所有引用对象的常用操作。由于引用对象是通过与垃圾回收器密切合作来实现的,因此,不能直接为此类创建子类。

    以上就是源码中对此类的一个说明,我们可能获得到的有用信息为:Reference类是基类且和GC是密切相关的。

    2.2、Reference类的4中状态

    在源码中,我们可以了解到,Reference有4种状态:

    1)、Active

    源码中对Active状态说明如下:

    Active: Subject to special treatment by the garbage collector. Some
    time after the collector detects that the reachability of the referent has changed to the appropriate state, it changes the instance’s state to either Pending or Inactive, depending upon
    whether or not the instance was registered with a queue when it was
    created. In the former case it also adds the instance to the
    pending-Reference list. Newly-created instances are Active.

    翻译为:Active状态的Reference会受到GC的特别关注,当GC察觉到引用的可达性变化为其它的状态之后,它的状态将变化为Pending或Inactive,到底转化为Pending状态还是Inactive状态取决于此Reference对象创建时是否注册了queue.如果注册了queue,则将添加此实例到pending-Reference list中。 新创建的Reference实例的状态是Active。

    每当我自己翻译的时候,都感觉翻译技术书籍的人真心不容易,很多东西我们自己知道是什么意思,但是当翻译过来的时候总是感觉没有描述清楚

    2)、Pending

    源码中对此状态给出的解释为:

    Pending: An element of the pending-Reference list, waiting to be
    enqueued by the Reference-handler thread. Unregistered instances
    are never in this state.

    翻译为:在pending-Reference list中等待着被Reference-handler 线程入队列queue中的元素就处于这个状态。没有注册queue的实例是永远不可能到达这一状态。

    3)、Enqueued

    源码中对此状态给出的解释为:

    Enqueued: An element of the queue with which the instance was
    registered when it was created. When an instance is removed from
    its ReferenceQueue, it is made Inactive. Unregistered instances are
    never in this state.

    翻译为:当实例被移动到ReferenceQueue中时,Reference的状态为Inactive。没有注册ReferenceQueue的不可能到达这一状态的。

    4)、Inactive

    源码中对此状态给出的解释为:

    Inactive: Nothing more to do. Once an instance becomes Inactive its
    state will never change again.

    翻译为:一旦一个实例变为Inactive,则这个状态永远都不会再被改变。

    以上就是Reference的4中状态的一个说明。

    2.3 Reference属性的介绍

    Reference属性的介绍

    在Reference中,从数据结构上看,Reference链表结构内部主要有:

        private T referent;         /* Treated specially by GC */
        Reference next;//指向下一个

    另一个相当重要的属性为:

      volatile ReferenceQueue<? super T> queue;
    

    这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。

    Queue的另外一个作用是可以区分不同状态的Reference。Reference有4中状态。分别为:Active、Pending、Enqueued和Inactive状态,关于这4中状态的具体含义在本文上面已经进行了简单的介绍。而这4中状态所对应的Queue如下:

    1、Active

    Reference类中源码给出的结果为:

        Active: queue = ReferenceQueue with which instance is registered, or
              ReferenceQueue.NULL if it was not registered with a queue; next =
              null

    至于为什么是这样的,我们可以从Reference类中的构造函数分析得到。

    Reference类中的构造函数为:

        /* -- Constructors -- */
    
        Reference(T referent) {
            this(referent, null);
        }
    
        Reference(T referent, ReferenceQueue<? super T> queue) {
            this.referent = referent;
            this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
        }

    由于Newly-created instances are Active.也就是利用构造出来的Reference实例都处于这样一个状态,而从构造函数可以看出,得到如下的结论:queue = ReferenceQueue with which instance is registered, or ReferenceQueue.NULL if it was not registered with a queue; next = null。

    2、Pending

    Reference类中源码给出的结果为:

         Pending: queue = ReferenceQueue with which instance is registered;
              next = this

    Pending状态的Reference实例的queue = ReferenceQueue with which instance is registered;这个我们都比较好理解,但是next = this ,这个目前我就找不到根源来分析得出了。

    3、Enqueued

    Reference类中源码给出的结果为:

         Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
              in queue, or this if at end of list.

    而为什么Enqueued状态的Reference实例的queue为ReferenceQueue.ENQUEUED以及next的值,我们是可以从ReferenceQueue中分析得到的??

    分析如下:从上面的介绍可知,当Reference实例入队列queue时,Reference状态就变为了Enqueued,而从ReferenceQueue的enqueue方法的源码中有这样两行代码r.queue = ENQUEUED;r.next = (head == null) ? r : head;,r.queue = ENQUEUED实现了对于入队的Reference的queue都进行了入队标识;这行代码r.next = (head == null) ? r : head;实现入队的Reference加在了链表的head位置

    4、Inactive

    Reference类中源码给出的结果为:

      Inactive: queue = ReferenceQueue.NULL; next = this.
    

    而为什么Inactive状态的Reference实例的queue为ReferenceQueue.NULL以及next = this,我们是可以从ReferenceQueue中分析得到的??

    分析如下:从上面的介绍可知,当Reference实例出队列queue时,Reference状态就变为了Inactive,而从ReferenceQueue的poll方法的源码中有这样两行代码r.queue = NULL;r.next = r;,r.queue = NULL实现了对于出队的Reference的queue都进行了出队标识;这行代码r.next = r;实现出队的Reference对象的next指向自己

    在Reference源码中,还有这样两个属性:

    1)、Reference类中的pending属性

    这个对象,定义为private的,但是全局没有任何地方给出他赋值的地方,根据下面的注释,我们可以了解到这个变量是和垃圾回收打交道的。

        /* List of References waiting to be enqueued.  The collector adds
         * References to this list, while the Reference-handler thread removes
         * them.  This list is protected by the above lock object. The
         * list uses the discovered field to link its elements.
         */
        private static Reference<Object> pending = null;

    2)、Reference类中的discovered属性

    与pending属性一样,同为private且上下文都没有任何地方使用它,在注释中,我们可以看到这个变量也是和垃圾回收打交道的。

        /* When active:   next element in a discovered reference list maintained by GC (or this if last)
         *     pending:   next element in the pending list (or null if last)
         *   otherwise:   NULL
         */
        transient private Reference<T> discovered;  /* used by VM */

    2.4、ReferenceHandler线程

    下面看Reference类中的ReferenceHandler线程

    源码如下,

    从源码中可以看出,这个线程在Reference类的static构造块中启动,并且被设置为最高优先级和daemon状态。此线程要做的事情就是不断的的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进行wait状态。

        private static class ReferenceHandler extends Thread {
    
            ReferenceHandler(ThreadGroup g, String name) {
                super(g, name);
            }
    
            public void run() {
                for (;;) {
                    Reference<Object> r;
                    synchronized (lock) {
                        if (pending != null) {
                            r = pending;
                            pending = r.discovered;
                            r.discovered = null;
                        } else {
                            // The waiting on the lock may cause an OOME because it may try to allocate
                            // exception objects, so also catch OOME here to avoid silent exit of the
                            // reference handler thread.
                            //
                            // Explicitly define the order of the two exceptions we catch here
                            // when waiting for the lock.
                            //
                            // We do not want to try to potentially load the InterruptedException class
                            // (which would be done if this was its first use, and InterruptedException
                            // were checked first) in this situation.
                            //
                            // This may lead to the VM not ever trying to load the InterruptedException
                            // class again.
                            try {
                                try {
                                    lock.wait();
                                } catch (OutOfMemoryError x) { }
                            } catch (InterruptedException x) { }
                            continue;
                        }
                    }
    
                    // Fast path for cleaners
                    if (r instanceof Cleaner) {
                        ((Cleaner)r).clean();
                        continue;
                    }
    
                    ReferenceQueue<Object> q = r.queue;
                    if (q != ReferenceQueue.NULL) q.enqueue(r);
                }
            }
        }
    
        static {
            ThreadGroup tg = Thread.currentThread().getThreadGroup();
            for (ThreadGroup tgn = tg;
                 tgn != null;
                 tg = tgn, tgn = tg.getParent());
            Thread handler = new ReferenceHandler(tg, "Reference Handler");
            /* If there were a special system-only priority greater than
             * MAX_PRIORITY, it would be used here
             */
            handler.setPriority(Thread.MAX_PRIORITY);
            handler.setDaemon(true);
            handler.start();
        }
    

    有了以上的基础,我们来看一个Reference的应用

        /*
         * 创建一个WeakReference,并且将其referent改变
         * */
        public class TestReference2 {
    
            public static void main(String[] args) {
                Object o = new Object();
                WeakReference<Object> wr = new WeakReference<Object>(o);
    
                System.out.println(wr.get());//java.lang.Object@19e0bfd
                o = null;
                System.gc();
                System.out.println(wr.get());//null
            }
    
        }
    

    上面的代码我们都比较容易的知道运行结果。但是至于内部实现的原因,是这样的。

    由于pending是由JVM来赋值的,当Reference内部的referent对象的可达状态发生改变时,JVM会将Reference对象放入到pending链表中。因此,在例子中的代码o = null这一句,它使得o对象满足垃圾回收的条件,并且在后边显示调用了System.gc(),垃圾收集进行的时候会标记WeakReference的referent的对象o为不可达(使得wr.get()==null),并且通过赋值给pending,触发ReferenceHandler线程来处理pending.ReferenceHandler线程要做的是将pending对象enqueue。但在这个程序中我们从构造函数传入的为null,即实际使用的是ReferenceQueue.NULL,ReferenceHandler线程判断queue如果为ReferenceQueue.NULL则不进行enqueue,如果不是,则进行enqueue操作。

    ReferenceQueue.NULL相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。WeakHashMap类中提供的就是一个由意义的ReferenceQueue,非ReferenceQueue.NULL。

    以上就是关于Reference类的一个介绍,可能会比较不好理解,因为确实不怎么好理解。

    4种引用

    我们都知道在Java中有4种引用,这四种引用从高到低分别为:

    1)、StrongReference

    这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个对象具有强引用,则垃圾回收器始终不会回收此对象。当内存不足时,JVM情愿抛出OOM异常使程序异常终止也不会靠回收强引用的对象来解决内存不足的问题。

    2)、SoftReference

    如果一个对象只有软引用,则在内存充足的情况下是不会回收此对象的,但是,在内部不足即将要抛出OOM异常时就会回收此对象来解决内存不足的问题。

        public class TestReference3 {
            private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
            public static void main(String[] args){
                Object obj = new Object();
                SoftReference<Object> sf = new SoftReference(obj,rq);
                System.out.println(sf.get()!=null);
                System.gc();
                obj = null;
                System.out.println(sf.get()!=null);
    
            }
        }

    运行结果均为:true。

    这也就说明了当内存充足的时候一个对象只有软引用也不会被JVM回收。

    3)、WeakReference

    WeakReference基本与SoftReference类似,只是回收的策略不同。

    只要GC发现一个对象只有弱引用,则就会回收此弱引用对象。但是由于GC所在的线程优先级比较低,不会立即发现所有弱引用对象并进行回收。只要GC对它所管辖的内存区域进行扫描时发现了弱引用对象就进行回收。

    看一个例子:

        public class TestWeakReference {
            private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
            public static void main(String[] args) {
                Object obj = new Object();
                WeakReference<Object> wr = new WeakReference(obj,rq);
                System.out.println(wr.get()!=null);
                obj = null;
                System.gc();
                System.out.println(wr.get()!=null);//false,这是因为WeakReference被回收
            }
    
        }
    

    运行结果为: true 、false

    在指向obj = null语句之前,Object对象有两条引用路径,其中一条为obj强引用类型,另一条为wr弱引用类型。此时无论如何也不会进行垃圾回收。当执行了obj = null.Object对象就只具有弱引用,并且我们进行了显示的垃圾回收。因此此具有弱引用的对象就被GC给回收了。

    4)、PhantomReference

    PhantomReference,即虚引用,虚引用并不会影响对象的生命周期。虚引用的作用为:跟踪垃圾回收器收集对象这一活动的情况。

    当GC一旦发现了虚引用对象,则会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference对象并没有被垃圾回收器回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。

    注意:PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。

    接下来看一个虚引用的例子。

        public class TestPhantomReference {
    
            private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
            public static void main(String[] args){
    
                Object obj = new Object();
                PhantomReference<Object> pr = new PhantomReference<Object>(obj, rq);
                System.out.println(pr.get());
                obj = null;
                System.gc();
                System.out.println(pr.get());
                Reference<Object> r = (Reference<Object>)rq.poll();
                if(r!=null){
                    System.out.println("回收");
                }
            }
        }
    

    运行结果:null null 回收

    根据上面的例子有两点需要说明:

    1)、PhantomReference的get方法无论在上面情况下都是返回null。这个在PhantomReference源码中可以看到。

    2)在上面的代码中,如果obj被置为null,当GC发现虚引用,GC会将把PhantomReference对象pr加入到队列ReferenceQueue中,注意此时pr所指向的对象并没有被回收,在我们现实的调用了rq.poll()返回Reference对象之后当GC第二次发现虚引用,而此时JVM将虚引用pr插入到队列rq会插入失败,此时GC才会对虚引用对象进行回收。

    下面对Reference的3个子类进行一个简要的说明。

    SoftReference类

    在JDK文档对此类的介绍如下:

    1)、软引用对象,在响应内存需要时,由垃圾回收器决定是否清除此对象。软引用对象最常用于实现内存敏感的缓存。

    2)、假定垃圾回收器确定在某一时间点某个对象是软可到达对象。这时,它可以选择自动清除针对该对象的所有软引用,以及通过强引用链从其可以到达该对象的针对任何其他软可到达对象的所有软引用。在同一时间或晚些时候,它会将那些已经向引用队列注册的新清除的软引用加入队列。

    3)、软可到达对象的所有软引用都要保证在虚拟机抛出 OutOfMemoryError 之前已经被清除。否则,清除软引用的时间或者清除不同对象的一组此类引用的顺序将不受任何约束。然而,虚拟机实现不鼓励清除最近访问或使用过的软引用。

    4)、此类的直接实例可用于实现简单缓存;该类或其派生的子类还可用于更大型的数据结构,以实现更复杂的缓存。只要软引用的指示对象是强可到达对象,即正在实际使用的对象,就不会清除软引用。例如,通过保持最近使用的项的强指示对象,并由垃圾回收器决定是否放弃剩余的项,复杂的缓存可以防止放弃最近使用的项。

    总结:SoftReference是软引用,只有在JVM在抛OOM异常时才会回收。其它情况下不会回收。

    WeakReference类

    在JDK文档对此类的介绍如下:

    1)、弱引用对象,它们并不禁止其指示对象变得可终结,并被终结,然后被回收。弱引用最常用于实现规范化的映射。

    2)、假定垃圾回收器确定在某一时间点上某个对象是弱可到达对象。这时,它将自动清除针对此对象的所有弱引用,以及通过强引用链和软引用,可以从其到达该对象的针对任何其他弱可到达对象的所有弱引用。同时它将声明所有以前的弱可到达对象为可终结的。在同一时间或晚些时候,它将那些已经向引用队列注册的新清除的弱引用加入队列。

    PhantomReference类

    在JDK文档对此类的介绍如下:

    1)、虚引用对象,在回收器确定其指示对象可另外回收之后,被加入队列。虚引用最常见的用法是以某种可能比使用 Java 终结机制更灵活的方式来指派 pre-mortem 清除动作。

    2)、如果垃圾回收器确定在某一特定时间点上虚引用的指示对象是虚可到达对象,那么在那时或者在以后的某一时间,它会将该引用加入队列。

    3)、为了确保可回收的对象仍然保持原状,虚引用的指示对象不能被获取:虚引用的 get 方法总是返回 null。

    4)、与软引用和弱引用不同,虚引用在加入队列时并没有通过垃圾回收器自动清除。通过虚引用可到达的对象将仍然保持原状,直到所有这类引用都被清除,或者它们都变得不可到达。

    应用

    最后,看一个在《Java编程思想》这本书上的一个例子

        public class References {
    
            private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();
            public static void checkQueue(){
                Reference<? extends VeryBig> inq = rq.poll();
                if(inq!=null){
                    System.out.println("In queue:"+inq.getClass().getSimpleName());
                }
            }
    
            public static void main(String[] args) {
                int size = 10;
                /*
                 * SoftReference:在内存不足时才会回收这样软引用对象
                 * */
                LinkedList<SoftReference<VeryBig>> sa = new  LinkedList<SoftReference<VeryBig>>();
                for(int i=0;i<size;i++){
                    sa.add(new SoftReference(new VeryBig("Soft "+i),rq));
                    System.out.println("Just created: "+sa.getLast().get());
                    checkQueue();//一直为空
                }
                /*
                 * WeakReference:在GC发现只具有弱引用的对象会立即对其会回收
                 * */
                LinkedList<WeakReference<VeryBig>> wa = new  LinkedList<WeakReference<VeryBig>>();
                for(int i=0;i<size;i++){
                    wa.add(new WeakReference(new VeryBig("Weak "+i),rq));
                    System.out.println("Just created: "+wa.getLast().get());
                    checkQueue();
                }
    
                SoftReference<VeryBig> sf = new SoftReference<VeryBig>(new VeryBig("Soft "));
                WeakReference<VeryBig> wf = new WeakReference<VeryBig>(new VeryBig("Weak"));
    
                System.gc();//显示的进行垃圾回收,什么时候执行就由JVM决定
    
                LinkedList<PhantomReference<VeryBig>> pa = new  LinkedList<PhantomReference<VeryBig>>();
                for(int i=0;i<size;i++){
                    pa.add(new PhantomReference(new VeryBig("Phantom "+i),rq));
                    System.out.println("Just created: "+pa.getLast());
                    checkQueue();
                }       
            }
    
        }
    
        class VeryBig{
            private static final int SIZE = 10000;
            private long[] la = new long[SIZE];
            private String ident;
            public VeryBig(String id){
                ident = id;
            }
            public String toString(){
                return ident;
            }
    
            protected void finalize(){
                System.out.println("Finalizing "+ ident);
            }
    
        }
    

    运行结果为:

        Just created: Soft 0
        Just created: Soft 1
        Just created: Soft 2
        Just created: Soft 3
        Just created: Soft 4
        Just created: Soft 5
        Just created: Soft 6
        Just created: Soft 7
        Just created: Soft 8
        Just created: Soft 9
        Just created: Weak 0
        Just created: Weak 1
        Just created: Weak 2
        Just created: Weak 3
        Just created: Weak 4
        Just created: Weak 5
        Just created: Weak 6
        Just created: Weak 7
        Just created: Weak 8
        Just created: Weak 9
        Just created: java.lang.ref.PhantomReference@19e0bfd
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@139a55
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@1db9742
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@106d69c
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@52e922
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@25154f
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@10dea4e
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@647e05
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@1909752
        In queue:WeakReference
        Just created: java.lang.ref.PhantomReference@1f96302
        In queue:WeakReference

    根据上面的知识点的介绍,理解这个程序应该不难。下面还是分析下:

    首先对于下一段代码,由于是SoftReference引用,因此只有在内存不足时才会被回收。

                LinkedList<SoftReference<VeryBig>> sa = new  LinkedList<SoftReference<VeryBig>>();
                for(int i=0;i<size;i++){
                    sa.add(new SoftReference(new VeryBig("Soft "+i),rq));
                    System.out.println("Just created: "+sa.getLast().get());
                    checkQueue();//一直为空
                }

    对于下面这段代码,由于是WeakReference引用,因此当GC发现其就会被回收。由于这段代码最后一句进行了gc的显示调用,因此这些具有弱引用的对象就都会被回收掉,在回收之前调用了对象的finalize的方法并将WeakReference加入的队列Queue中。

                LinkedList<WeakReference<VeryBig>> wa = new  LinkedList<WeakReference<VeryBig>>();
                for(int i=0;i<size;i++){
                    wa.add(new WeakReference(new VeryBig("Weak "+i),rq));
                    System.out.println("Just created: "+wa.getLast().get());
                    checkQueue();
                }
    
                System.gc();//显示的进行垃圾回收,什么时候执行就由JVM决定
    

    对于最后一段代码,在执行这段代码之前,如果调用System.gc()后JVM立即进行垃圾回收则队列queue装的是已经被回收的WeakReference引用,因此,在下段代码中的每个for循环中调用的checkQueue()函数就有输出了

    由于是PhantomReference,因此在垃圾回收前要将PhantomReference加入到其注册的队列中。

    
                LinkedList<PhantomReference<VeryBig>> pa = new  LinkedList<PhantomReference<VeryBig>>();
                for(int i=0;i<size;i++){
                    pa.add(new PhantomReference(new VeryBig("Phantom "+i),rq));
                    System.out.println("Just created: "+pa.getLast());
                    checkQueue();
                }

    如果我们在上面代码后面也添加一个System.gc(),则也会对虚引用对象进行一个垃圾回收。

    最后要说明的是:上面程序的运行结果每次运行可能都不会一致,这是因为当我们显示调用System.gc();时JVM虚拟机有时并不会立即执行。

    例如:不立即执行的其中一种情况的结果为:

        Just created: Soft 0
        Just created: Soft 1
        Just created: Soft 2
        Just created: Soft 3
        Just created: Soft 4
        Just created: Soft 5
        Just created: Soft 6
        Just created: Soft 7
        Just created: Soft 8
        Just created: Soft 9
        Just created: Weak 0
        Just created: Weak 1
        Just created: Weak 2
        Just created: Weak 3
        Just created: Weak 4
        Just created: Weak 5
        Just created: Weak 6
        Just created: Weak 7
        Just created: Weak 8
        Just created: Weak 9
        Just created: java.lang.ref.PhantomReference@19e0bfd
        Just created: java.lang.ref.PhantomReference@139a55
        Just created: java.lang.ref.PhantomReference@1db9742
        Just created: java.lang.ref.PhantomReference@106d69c
        Just created: java.lang.ref.PhantomReference@52e922
        Just created: java.lang.ref.PhantomReference@25154f
        Just created: java.lang.ref.PhantomReference@10dea4e
        Just created: java.lang.ref.PhantomReference@647e05
        Just created: java.lang.ref.PhantomReference@1909752
        Just created: java.lang.ref.PhantomReference@1f96302
        Finalizing Weak
        Finalizing Weak 9
        Finalizing Weak 8
        Finalizing Weak 7
        Finalizing Weak 6
        Finalizing Weak 5
        Finalizing Weak 4
        Finalizing Weak 3
        Finalizing Weak 2
        Finalizing Weak 1
        Finalizing Weak 0

    参考资料

    1、http://hongjiang.info/java-referencequeue/

    2、http://blog.sina.com.cn/s/blog_667ac0360102e9f3.html

    展开全文
  • java源码包2

    千次下载 热门讨论 2013-04-20 11:28:17
    内容索引:JAVA源码,媒体网络,飞鸽传  Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升...
  • 在实习期间遇到最多的两个框架就是spring和paoding-rose了,所以看完spring源码分析,我就迫不及待的开始找paoding-rose的了。可惜没找到,所以就自己动手分析吧。 spring源码分析可以在这里下载: ...
  • 点进文章的盆友不如先来做一道非常常见的面试题,如果你能做出来,可能你早已掌握并理解了java的类加载机制,若结果出乎你的意料,那就很有必要来了解了解java的类加载机制了。代码如下嗯哼?其实上面程序并不是关键...
  • java源码包3

    千次下载 热门讨论 2013-04-20 11:30:13
    内容索引:JAVA源码,媒体网络,飞鸽传  Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升...
  • java源码包4

    千次下载 热门讨论 2013-04-20 11:31:44
    内容索引:JAVA源码,媒体网络,飞鸽传  Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升...
  • Java虚拟机hotspot源码分析之找门

    千次阅读 2014-09-15 18:48:45
    像我这样Java学个半吊子,就开始研究JVM源码的人实在是奇葩的存在!!! 源码据说有50多万行,不过感觉也不是很多的样子。大概是linux源码看多了,觉得这hotspot并不是 很大。先记录一下成果吧! 首先,...
  • Java8源码分析】集合框架-HashMap

    千次阅读 2017-05-10 22:53:11
    三、源码分析 package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java...
  • JavaWeb——Servlet Tomcat工作机制动画演示(点击动图可...Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web...
  • Java的Object类的源码分析

    千次阅读 2018-08-07 11:40:40
    下面是我对Object类中个方法的认识(结合了代码中的注释以及看过的一些) public class Object { //其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。函数的执行是在静态代码块中执行...
  • JVM源码分析-Java运行

    千次阅读 2014-08-06 21:21:40
    最近在看Java并发编程实践和Inside JVM两本,发现如果不真正的了解底层运作,那么永远是雾里看花。因此从http://openjdk.java.net/groups/hotspot/上下载了源代码,准备研究一番。要想完全研究懂我觉得得对计算机...
  • 关于java中ReentrantLock类的源码分析以及总结与例子
  • ConcurrentHashMap是Java5中新增加的一个线程安全的Map集合,可以用来替代HashTable。对于ConcurrentHashMap是如何提高其效率的,可能大多人只是知道它使用了多个锁代替HashTable中的单个锁,也就是锁分离技术(Lock...
  • java源码书籍推荐

    千次阅读 2018-08-13 09:20:08
    有没有一些java源码书籍推荐,最好是同时提供分析源码思路的书籍
  • 一个简单的java计算器源码分析

    千次阅读 2006-12-10 22:58:00
    读朱仲杰老师的有一段时间了,个人觉得他的《java2全方位学习(J2SE增修版)》很不错,应该很适合初学者吧。比较惭愧的是我的进展还比较缓慢,学到GUI部分了,前边的习题倒是跟着作了些,都还比较基础,看来是该...
  • 前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几天...
  • There is no getter for property named '*' in 'class java.lang.String',此错误之所以出现,是因为mybatis在对parameterType="String"的sql语句做了限制,假如你使用!= null">这样的条件判断时,就会出现该错误,...
  • Java数据结构]从源码分析HashMap

    千次阅读 2016-05-09 10:33:39
    进一步阅读HashMap的put()方法源码,可以看到当put()操作有冲突时,新的Entry依然会被安放在对应的索引下标内,并替换原有的值。同时为了保证旧值不丢失,会将新的Entry的next指向旧值。这便实现了在一个数组索引...
  • jdk 源码分析(20)java NIO包简单分析

    千次阅读 2017-08-08 22:05:16
    优点,采用多路复用,而且因为采用buffer机制,当读写buffer时不需要阻塞。 nio 也有缺点,因为nio需要很多代码去出去半包问题,而底层采用epoll也是有问题,这些问题在多并发是可能出现,因为这些问题,所以出现...
  • 最近也看了一些其源码分析的文章以及亲自查看了源码,发现其对Java网络编程及HTTP权威指南有了一个很好的诠释。一直以来,都信奉一个原则,在这个新技术日新月异的时代,如何在Java界立足,凭借的还是基本功,包括:...
  • 自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的《深入理解Spark:核心思想与源码分析》一现在已经正式出版上市,目前亚马逊、京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买...
  • 马克·艾伦·维斯 的《数据结构与算法Java语言描述·第三版》随书源码 Mark Allen Weiss 作者的主页: https://users.cs.fiu.edu/~weiss/#c++java 本书源码的下载地址: https://users.cs.fiu.edu/~weiss/dsaajava3/...
  • java源码src分析出抽象语法树ast,方便做项目下所有类的分析,生成的语法可以方便的查找包名,类名,导入名,方法名等等 下面是一个入门demo,以了解如何方便使用 //只有parser &amp;lt;dependency&...
  • Java集合源码剖析-Java集合框架

    千次阅读 2017-12-31 22:14:21
    Hi大家好,我是清和二七,今天我们来聊聊《Java集合源码剖析-Java集合框架》 一.层次关系 Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组、链表、栈、队列、集合、哈希表等。学习Java集合...
  • springIOC底层源码分析

    2019-05-15 14:25:03
    本课程来自鲁班学院Java架构师VIP课程中框架源码专题中springIOC源码分析中的部分内

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 59,719
精华内容 23,887
关键字:

java源码分析的书

java 订阅