java 学习资料总结

1. java集合深入理解

https://blog.csdn.net/u011240877/article/category/6447444
http://www.cnblogs.com/skywang12345/p/3323085.html

1.1 迭代器的快速失败机制 fast-fail

https://blog.csdn.net/chewbee/article/details/78314661
1. 是java集合的一种错误检测机制
2. 防止一个线程在对集合进行迭代时候,另一个线程在结构上对集合进行修改,不是单纯的修改集合元素值

1.2 collection 集合

https://blog.csdn.net/justloveyou_/article/details/52948661

1.3 List集合

https://blog.csdn.net/justloveyou_/article/details/52955619

1.3.1 ArrayList

a. 内部数据结构

  private transient Object[] elementData;                 // 瞬时域
  private int size;

b. 扩容操作 这是一个public 函数,默认每次扩容是 1.5倍 + 1

// 调整数组容量
 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:
            // 最终会调用 System.ArrayCopy() 这是一个内存块拷贝函数
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
 }

c. 序列化 虽然内部数据结构一个是瞬态数据和一个基本数据类型 ,但是通过自定义的readObject()/writeObject()

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

d. ArrayList 允许值为null

// 移除此列表中 “首次” 出现的指定元素(如果存在)。这是因为 ArrayList 中允许存放重复的元素。  
 public boolean remove(Object o) {  
    // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。  
    if (o == null) {  
        for (int index = 0; index < size; index++)  
            if (elementData[index] == null) {  
                // 类似remove(int index),移除列表中指定位置上的元素。  
                fastRemove(index);  
                return true;  
            }  
    } else {  
        for (int index = 0; index < size; index++)  
            if (o.equals(elementData[index])) {  
                fastRemove(index);  
                return true;  
            }  
        }  
        return false;  
    } 
}

a. 头结点不存放任何数据
b. 新增元素放在链表尾部
c. 只能遍历访问,不能随机访问

2. Map 综述

https://blog.csdn.net/justloveyou_/

2.1 彻头彻尾理解 HashMap

https://blog.csdn.net/justloveyou_/article/details/62893086
这里写图片描述

初始容量: 桶的数量
负载因子: 扩容阈值=负载因子 * 初始容量

  1. 有两个重要的数据: 容量(默认是16),负载因子(默认是0.75)
  2. 使用数组链表的结构来存储
  3. put操作
  4. get操作
  5. resize扩容操作 多线程的情况下出现死循环
  6. 非线程安全的
  7. 迭代器的快速失败机制fast-fail机制

    在使用迭代器遍历Hashmap的过程中,别的线程不允许修改hashmap,要不然就会出现快速失败,内部是通过modCount计算器来实现的.

  8. 多线程并发HashMap,写操作导致扩容

    1. 内部的链表产生死循环 cpu100%
    2. 获取null
    3. 元素丢失
      https://www.cnblogs.com/dongguacai/p/5599100.html
      https://blog.csdn.net/u013668852/article/details/77141842
 /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with key, or null if there was no mapping for key.
     *  Note that a null return can also indicate that the map previously associated null with key.
     */
    public V put(K key, V value) {
        //当key为null时,调用putForNullKey方法,并将该键值对保存到table的第一个位置 
        if (key == null)
            return putForNullKey(value); 
        //根据key的hashCode计算hash值
        int hash = hash(key.hashCode());             //  ------- (1)
        //计算该键值对在数组中的存储位置(哪个桶)
        int i = indexFor(hash, table.length);              // ------- (2)
        //在table的第i个桶上进行迭代,寻找 key 保存的位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {      // ------- (3)
            Object k;
            //判断该条链上是否存在hash值相同且key值相等的映射,若存在,则直接覆盖 value,并返回旧value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;    // 返回旧值
            }
        }
        modCount++; //修改次数增加1,快速失败机制
        //原HashMap中无该映射,将该添加至该链的链头
        addEntry(hash, key, value, i);            
        return null;
    }

2.2彻头彻尾理解 LinkedHashMap

https://blog.csdn.net/justloveyou_/article/details/71713781
这里写图片描述
这里写图片描述
0. 增加属性: 链表头节点header , 标志位accessOrder,默认是保持插入顺序
1. 在HashMap的基础上增加双向链表
2. 默认是保持插入顺序
3. LUR算法(最近最少使用)
1. HashMap与LinkHashMap 的get/set方法会访问accessOrder() ,但是HashMap的accessOrder()是空操作
2. LinkHashMap的accessOrder(),如果对应的accessOrder设置为true recordAccess()函数在访问内部节点的时候就会把访问节点放到队列的尾部,并删除当前节点

新增节点

/**
     * This override alters behavior of superclass put method. It causes newly
     * allocated entry to get inserted at the end of the linked list and
     * removes the eldest entry if appropriate.
     *
     * LinkedHashMap中的addEntry方法
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {   

        //创建新的Entry,并插入到LinkedHashMap中  
        createEntry(hash, key, value, bucketIndex);  // 重写了HashMap中的createEntry方法

        //双向链表的第一个有效节点(header后的那个节点)为最近最少使用的节点,这是用来支持LRU算法的
        Entry<K,V> eldest = header.after;  
        //如果有必要,则删除掉该近期最少使用的节点,  
        //这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理的。  
        if (removeEldestEntry(eldest)) {  
            removeEntryForKey(eldest.key);  
        } else {  
            //扩容到原来的2倍  
            if (size >= threshold)  
                resize(2 * table.length);  
        }  
    } 

    void createEntry(int hash, K key, V value, int bucketIndex) { 
        // 向哈希表中插入Entry,这点与HashMap中相同 
        //创建新的Entry并将其链入到数组对应桶的链表的头结点处, 
        HashMap.Entry<K,V> old = table[bucketIndex];  
        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
        table[bucketIndex] = e;     

        //在每次向哈希表插入Entry的同时,都会将其插入到双向链表的尾部,  
        //这样就按照Entry插入LinkedHashMap的先后顺序来迭代元素(LinkedHashMap根据双向链表重写了迭代器)
        //同时,新put进来的Entry是最近访问的Entry,把其放在链表末尾 ,也符合LRU算法的实现  
        e.addBefore(header);  
        size++;  
    }

recordAccess() HashMap是一个空函数,LinkHashMap如下所示:

     void recordAccess(HashMap<K,V> m) {  
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
        //如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部,  
        //如果是按照插入的先后顺序排序,则不做任何事情。  
        if (lm.accessOrder) {  
            lm.modCount++;  
            //移除当前访问的Entry  
            remove();  
            //将当前访问的Entry插入到链表的尾部  
            addBefore(lm.header);  
          }  
      }
  1. 使用LinkHashMap实现LRU算法
public class LRU<K,V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 1L;
    public LRU(int initialCapacity,float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    /* LinkHashMap中的实现就是单纯的返回一个false,也就是不删除节点
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
*/
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        // TODO Auto-generated method stub
        if(size() > 6){
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        LRU<Character, Integer> lru = new LRU<Character, Integer>(16, 0.75f, true);
        String s = "abcdefghijkl";
        for (int i = 0; i < s.length(); i++) {
            lru.put(s.charAt(i), i);
        }
        System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));
        System.out.println("LRU的大小 :" + lru.size());
        System.out.println("LRU :" + lru);
    }
}

2.3 彻头彻尾理解 ConcurrentHashMap

https://blog.csdn.net/justloveyou_/article/details/72783008
ConcurrentHashMap的数据结构
a. segment继承ReentrantLock ,使得对象具备了锁的功能
b. 初始容量,负载因子,并发级别 三个重要参数 . 默认值分别为16 , 0.75 , 16

构造函数 : 根据初始容量,并发级别,确定 多少各segment 以及每个segment多少个桶

 public ConcurrentHashMap(int initialCapacity,  float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();

        if (concurrencyLevel > MAX_SEGMENTS)              
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;            // 大小为 lg(ssize) 
        int ssize = 1;            // 段的数目,segments数组的大小(2的幂次方)
        // 并发级别可能不是2的幂次方 ,转换一下
        // 假设并发级别是5,那么就会分为8段
        while (ssize < concurrencyLevel) {
            ++sshift; 
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;      // 用于定位段
        segmentMask = ssize - 1;      // 用于定位段

    // 
        this.segments = Segment.newArray(ssize);   // 创建segments数组

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;    // 总的桶数/总的段数 = 平均每个段中的桶数目 ,但是不是 2 幂次方
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;     // 每个段所拥有的桶的数目(2的幂次方)
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)      // 初始化segments数组
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }

c. put操作

d. get操作

// Segment 类
 V get(Object key, int hash) {
            if (count != 0) {            // read-volatile,首先读 count 变量
                HashEntry<K,V> e = getFirst(hash);   // 获取桶中链表头结点
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key)) {    // 查找链中是否存在指定Key的键值对
                        V v = e.value;
                        if (v != null)  // 如果读到value域不为 null,直接返回
                            return v;   
                        // 如果读到value域为null,说明发生了重排序,加锁后重新读取
                        return readValueUnderLock(e); // recheck
                    }
                    e = e.next;
                }
            }
     return null;  // 如果不存在,直接返回null
}

e. rehash操作
f. size() 统计大小操作

  1. 不加锁重试2次,每次去统计segment中的数量的时候,计入modCount的大小, 所有的段统计完成,去比较一下每个段中的modCount是否产生变化
  2. 如果产生变化,那么就对所有的段加锁,再次统计一下

这里写图片描述

g. 与JDK1.8实现的ConcurrentHashMap对比
https://blog.csdn.net/fouy_yun/article/details/77816587
1. 取消了分段
2. 链表长度默认超过8 ,变成红黑树
这里写图片描述

put操作 部分添加操作加锁

1. 若table[]未创建,则初始化。
2. 当table[i]后面无节点时,直接创建Node(无锁操作)。  cas原子操作 
3. 如果当前正在扩容,则帮助扩容并返回最新table[]。
4. 然后在链表或者红黑树中追加节点。 锁住当前table[i]
5. 最后还回去判断是否到达阀值,如到达变为红黑树结构。

get操作 整个过程都没有加锁

1. 首先定位到table[]中的i。
2. 若table[i]存在,则继续查找。
3. 首先比较链表头部,如果是则返回。
4. 然后如果为红黑树,查找树。
5. 最后再循环链表查找。  

2.4 彻头彻尾理解 HashTable

https://blog.csdn.net/justloveyou_/article/details/72862373

  1. Hashtable不同于HashMap,前者既不允许key为null,也不允许value为null;
  2. HashMap中用于定位桶位的Key的hash的计算过程要比Hashtable复杂一点,没有Hashtable如此简单、直接;
  3. 在HashMap的插入K/V对的过程中,总是先插入后检查是否需要扩容;而Hashtable则是先检查是否需要扩容后插入;
  4. Hashtable不同于HashMap,前者的put操作是线程安全的


    put操作 :

    1. 方法同步
    2. 不允许空value
    3. 没有对key做出null处理,暗示key不能为null
    4. 计算桶的位置使用的是取模运算,也不是HashMap的&运算,也就是说HashTable的初始容量没有什么要求
    5. 先判断是否需要扩容,再插入节点

 public synchronized V put(K key, V value) {     // 加锁同步,保证Hashtable的线程安全性
        // Make sure the value is not null
        if (value == null) {      // 不同于HashMap,Hashtable不允许空的value
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = key.hashCode();   // key 的哈希值,同时也暗示Hashtable不同于HashMap,其不允许空的key
        int index = (hash & 0x7FFFFFFF) % tab.length;   // 取余计算节点存放桶位,0x7FFFFFFF 是最大的int型数的二进制表示
        // 先查找Hashtable上述桶位中是否包含具有相同Key的K/V对
        for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        // 向Hashtable中插入目标K/V对
        modCount++;     // 发生结构性改变,modCount加1
        if (count >= threshold) {    //在插入目标K/V对前,先检查是否需要扩容(不同于HashMap的插入后检查是否需要扩容) 
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            index = (hash & 0x7FFFFFFF) % tab.length;   // 扩容后,重新计算K/V对插入的桶位
        }

        // Creates the new entry.
        Entry<K, V> e = tab[index];
        tab[index] = new Entry<K, V>(hash, key, value, e); // 将K/V对链入对应桶中链表,并成为头结点
        count++;     // Hashtable中Entry数目加1
        return null;
    }

get操作

  1. 同步方法
  2. 取模的方式定位桶位置
  3. 在链表中查找指定的key ,没找到返回null
public synchronized V get(Object key) {    // 不同于HashMap,Hashtable的读取操作是同步的
        Entry tab[] = table;
        int hash = key.hashCode();   
        int index = (hash & 0x7FFFFFFF) % tab.length;   // 定位K/V对的桶位
        for (Entry<K, V> e = tab[index]; e != null; e = e.next) {   // 在特定桶中依次查找指定Key的K/V对
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;       
            }
        }
        return null;   // 查找失败
    }

3. java面试锦集

https://blog.csdn.net/justloveyou_/article/details/78653660 https://blog.csdn.net/justloveyou_/article/details/78653929 https://blog.csdn.net/justloveyou_/article/details/78313167

4. String

https://blog.csdn.net/justloveyou_/article/details/52556427 https://blog.csdn.net/justloveyou_/article/details/60983034

1.String

对String的一般认识
  1. 类使用了final修饰,不能被继承
  2. 内部字段也被final修饰,初始化就不能再次修改
  3. String是一个不可变类,线程安全
  4. 内部使用的是char[]来表示
编译期间就会把字面字符串放到字符串常量池 字符串拼接操作
  1. 字符串字面量拼接,标量替换
  2. 字符串引用拼接 ,还是会调用StringBuilder.append toString返回一个String对象
引用拼接的本质:
String s4 = s1 + s2 + s3;
====>
String s4 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).toString();

2.StringBuilder

  1. 不是线程安全的类
  2. 是单线程下替换StringBuffer的一个类

3. StringBuffer

  1. 是一个线程安全的类

4. 总结

  1. 使用字面值形式创建字符串时,不一定会创建对象,但其引用一定指向位于字符串常量池的某个对象;
  2. 使用 new String(“…”)方式创建字符串时,一定会创建对象,甚至可能会同时创建两个对象(一个位于字符串常量池中,一个位于堆中);
  3. String 对象是不可变的,对String 对象的任何改变都会导致一个新的 String 对象的产生,而不会影响到原String 对象;
  4. StringBuilder 与 StringBuffer 具有共同的父类,具有相同的API,分别适用于单线程和多线程环境下。特别地,在单线程环境下,StringBuilder 是 StringBuffer 的替代品,前者效率相对较高;

5. 泛型

https://blog.csdn.net/s10461/article/details/53941091
泛型遇上多态
https://blog.csdn.net/lonelyroamer/article/details/7868820

6.注解

https://blog.csdn.net/briblue/article/details/73824058
https://www.cnblogs.com/Qian123/p/5256084.html

7.java io

7.1 File

7.2 字节流字符流

7.3 Pushback流

7.4 RandomAccessFile

7.5 序列化

自定义readObject函数的调用过程
https://blog.csdn.net/xiaoanian/article/details/9064635
https://www.cnblogs.com/yoohot/p/6019767.html

a.序列化算法

  1. 所有的对象都有一个序列化编号
  2. 检查对象是否已经被序列化
  3. 如果已经序列化,那么只输出序列化编号

note: 注意可变对象的序列化

b.反序列化创建对象

  1. 获取反序列化对象的元数据(Class文件) ——> 该类还没有被加载,那就执行类加载机制
  2. 在内存中创建内存空间
  3. 直接内存赋值
  4. 不需要调用构造函数

c.自定义序列化

  1. 实现Serializable接口,该接口就是一个标记,内部没有任何东西
  2. 实现Externalizable接口

d.注意事项

  1. 父类实现了序列化,子类自动实现序列化
  2. 基类没有实现序列化
    1. 如果 存在无参构造函数,那么序列化过程中不会报错,但是不会保存实例变量值
    2. 如果不存在无参构造函数,那么序列化过程中直接报错
  3. transient修饰的变量不参与序列化,类变量也不参与序列化

8 线程

8.1 创建线程的三种方式

  1. 继承Thread
  2. 实现Runnable接口
  3. 实现Callable接口

8.2 Daemon线程

  1. 需要在线程启动之前设置为后台线程,否则线程启动之后,线程状态是Runnable,而在setDaemon函数中会判断线程状态是不是new状态,不是就报错了
  2. 所有的前台线程退出了,那么后台线程也就退出了
  3. Daemon线程中再次创建线程也是Daemon线程

8.3 interrupt的理解

https://www.ibm.com/developerworks/cn/java/j-jtp05236.html
https://www.cnblogs.com/timlearn/p/4008783.html
1. java中断是一种协调机制
2. 线程对象调用interrupt函数向线程发送一个中断信号
3. 在线程内部,如果线程处于阻塞,那么就会抛出InterruptException异常;如果处于运行状态,那么就会设置一下该线程的中断状态为真
4. isInterrupted()函数返回线程现在是不是中断状态,但是不会影响中断状态
5. interrupted()静态函数,会重置中断状态

8.4 synchronized volatile

  1. volatile
    1. 保证各线程的可见性,但是不保证原子性.线程的工作内存马上同步到主内存中
    2. 禁止指令重排序
    3. 轻量级锁
      https://www.cnblogs.com/wq3435/p/6220751.html
      https://blog.csdn.net/justloveyou_/article/details/53672005
  2. synchronized可以保证

8.5 wait notify

https://www.cnblogs.com/hapjin/p/5492645.html
线程状态转移
https://blog.csdn.net/pange1991/article/details/53860651
问题1: 为什么会假唤醒?
问题2: 为什么需要需要同步?

synchronized(object){
  // 使用while防止假唤醒
  while(条件不满足){
    object.wait();
  }
  //逻辑处理;
}

synchronized(object){
  //改变条件
  object.notifyAll();
}
  1. 对象的wait notify方法必须在同步快内部,也就是说需要获取对象锁才可以调用执行
  2. notify()执行,出了同步快,那么就会唤醒该对象等待队列上的线程,进入同步队列
  3. wait()执行,放弃cpu资源,释放锁,线程放进等待队列中,等待唤醒

4. Lock

5. 并发容器

6. 偏向锁,轻量锁,重量级锁

这里写图片描述

7 内存屏障

http://ifeve.com/disruptor-memory-barrier/

8 可重入锁解析 ReentrantLock

http://www.cnblogs.com/xrq730/p/4979021.html
https://blog.csdn.net/lsgqjh/article/details/63685058

1.加锁过程

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { //如果没有线程获取锁,则当前线程CAS获取锁。并设置自己为当前锁的独占线程
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {// 如果存在锁竞争,判断获取锁的线程是否是当前线程, 如果是 那么根据可重入的含义, 使当前state+1;
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }如果不是当前线程,则不能获取同步状态
            return false;    
 }
  1. 获取volatile修饰的state值
  2. 如果state的值为0,说明还没有线程持有锁
    1. 使用CAS操作设置state的值
    2. 设置当前线程持有锁
  3. state不为0,说明当前有线程持有该锁,那么判断是不是当前线程想重入

    1. 如果是,重新设置state的值
    2. 如果不是,获取锁失败

    2.解锁过程

    protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //  只有获得锁的线程自己才能释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//c==0说明已经无锁
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            //否则更新state为state-1, 也就是加锁多少次,就得释放多少次, lock unlock配对使用。
    
            return free;
        }
    1. 当前线程不持有锁,解锁异常抛出
    2. 获取state值
      1. 如果state的值为0了,那么说明解锁完就没有那个对象持有该锁了,需要设置锁没有持有任何线程
      2. 重新设置state

9 final

https://www.cnblogs.com/senlinyang/p/7875468.html
1. 写final域的重排序规则:禁止把final域的写重排序到构造函数之外——>对象的引用在任意线程可见之前,final域一定是已经正确初始化过的
通过下面2种方式保证
a. JMM禁止编译器把final域写重排序到构造函数之外
b. 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore内存屏障

2. 读final域的重排序规则: 初次读对象引用和初次读对象包含的final域两个操作之间禁止重排序,编译器会在两个操作之间插入LoadLoad内存屏障——–>在读一个对象的final域之前,一定会先读取包含这个final域的对象的引用
3. final域是一个引用类型,final域对象内部成员的初始化和使用对象之间禁止重排序
final域可以给我们一下保证: 只要对象正确的构造,那么不需要同步,任意线程都可以看到final域在构造函数中初始化值

10 深拷贝VS浅拷贝

猜你喜欢

转载自blog.csdn.net/tanliqing2010/article/details/79940275