diff --git a/second/week_01/86/HashMap.md b/second/week_01/86/HashMap.md new file mode 100644 index 0000000000000000000000000000000000000000..4a7f0aea5197afbec1160e22b1e39013d64eb7e3 --- /dev/null +++ b/second/week_01/86/HashMap.md @@ -0,0 +1,262 @@ + +## 1、简介 + +`HashMap` 是一个关联、线程不安全的数组,它是基于哈希表`Map`接口实现的,以`key-value`键值对的结构存储。 +在遇到冲突的时候会使用链表来进行解决,JDK8以后引入来红黑树的模式。 + +HashMap 是一个线程不安全的,key 和 value 都允许为空,key 重复会覆盖、Value 允许重复。在多线程中,我们一般使用 concurrentHashMap 保证它的线程安全。 + +## 2、常用方法的作用 +**HashMap的属性** +``` +//默认容量 +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; +//最大容量 +static final int MAXIMUM_CAPACITY = 1 << 30; +//默认加载因子 +static final float DEFAULT_LOAD_FACTOR = 0.75f; +//链表转成红黑树的阈值 +static final int TREEIFY_THRESHOLD = 8; +//红黑树转为链表的阈值 +static final int UNTREEIFY_THRESHOLD = 6; +//存储方式由链表转成红黑树的容量的最小阈值 +static final int MIN_TREEIFY_CAPACITY = 64; +//HashMap中存储的键值对的数量 +transient int size; +//扩容阈值,当size>=threshold时,就会扩容 +int threshold; +//HashMap的加载因子 +final float loadFactor; +``` +这里我们需要加载因子(load_factor),加载因子默认为0.75,当HashMap中存储的元素的数量大于(容量×加载因子),也就是默认大于16*0.75=12时,HashMap会进行扩容的操作。 + +## 3、常用方法的介绍 + +### 3.1 初始化 + +``` +/** 构造方法 1 */ +public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted +} + +/** 构造方法 2 */ +public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); +} + +/** 构造方法 3 */ +public HashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); +} + +/** 构造方法 4 */ +public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); +} +``` + + +### 3.2 查找 + +HashMap是通过先定位键值对所在桶的位置,然后再对链表或红黑树进行查找,通过这种方式实现查找,代码如下 + +``` +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + // 1. 定位键值对所在桶的位置 + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + if ((e = first.next) != null) { + // 2. 如果 first 是 TreeNode 类型,则调用黑红树查找方法 + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key); + + // 2. 对链表进行查找 + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; +} +``` + +### 3.3 遍历 + +``` +for(Object key : map.keySet()) { + // do something +} + +或 + +for(HashMap.Entry entry : map.entrySet()) { + // do something +} + +等价于 + +Set keys = map.keySet(); +Iterator ite = keys.iterator(); +while (ite.hasNext()) { + Object key = ite.next(); + // do something +} + + +``` + +### 3.4 插入 +插入操作的入口方法是 put(K,V),但核心逻辑在V putVal(int, K, V, boolean, boolean) 方法中。putVal 方法主要做了这么几件事情: + +当桶数组 table 为空时,通过扩容的方式初始化 table +查找要插入的键值对是否已经存在,存在的话根据条件判断是否用新值替换旧值 +如果不存在,则将键值对链入链表中,并根据链表长度决定是否将链表转为红黑树 +判断键值对数量是否大于阈值,大于的话则进行扩容操作 +``` +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // 初始化桶数组 table,table 被延迟到插入新数据时再进行初始化 + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + // 如果桶中不包含键值对节点引用,则将新键值对节点的引用存入桶中即可 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + // 如果键的值以及节点 hash 等于链表中的第一个键值对节点时,则将 e 指向该键值对 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + + // 如果桶中的引用类型为 TreeNode,则调用红黑树的插入方法 + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + // 对链表进行遍历,并统计链表长度 + for (int binCount = 0; ; ++binCount) { + // 链表中不包含要插入的键值对节点时,则将该节点接在链表的最后 + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + // 如果链表长度大于或等于树化阈值,则进行树化操作 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + + // 条件为 true,表示当前链表包含要插入的键值对,终止遍历 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; + } + } + + // 判断要插入的键值对是否存在 HashMap 中 + if (e != null) { // existing mapping for key + V oldValue = e.value; + // onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值 + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); + return oldValue; + } + } + ++modCount; + // 键值对数量超过阈值时,则进行扩容 + if (++size > threshold) + resize(); + afterNodeInsertion(evict); + return null; +} +``` + +### 3.5 删除 +HashMap 的删除操作并不复杂,仅需三个步骤即可完成。第一步是定位桶位置,第二步遍历链表并找到键值相等的节点,第三步删除节点。 +``` +public V remove(Object key) { + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; +} + +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + if ((tab = table) != null && (n = tab.length) > 0 && + // 1. 定位桶位置 + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + // 如果键的值与链表第一个节点相等,则将 node 指向该节点 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + else if ((e = p.next) != null) { + // 如果是 TreeNode 类型,调用红黑树的查找逻辑定位待删除节点 + if (p instanceof TreeNode) + node = ((TreeNode)p).getTreeNode(hash, key); + else { + // 2. 遍历链表,找到待删除节点 + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + } + + // 3. 删除节点,并修复链表或红黑树 + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) + ((TreeNode)node).removeTreeNode(this, tab, movable); + else if (node == p) + tab[index] = node.next; + else + p.next = node.next; + ++modCount; + --size; + afterNodeRemoval(node); + return node; + } + } + return null; +} +``` + + +## 4、注意事项 + +`ArrayList`以数组形式实现,按顺序插入、查找快、插入、删除慢 +`LinkedList`以链表形式实现,按顺序插入、查找慢,插入、删除方便 +`HashMap`集合两者的优点 diff --git a/second/week_01/86/LinkedList.md b/second/week_01/86/LinkedList.md new file mode 100644 index 0000000000000000000000000000000000000000..0934c4578a552ea602901868bb55748f633321ce --- /dev/null +++ b/second/week_01/86/LinkedList.md @@ -0,0 +1,320 @@ + +## 1、简介 +LinkedList 是一个线程不安全,允许元素为 Null 的双向链表。它的底层数据结构是链表,因为实现List,Deque,Cloneable,java.io.Serializable,它也可以作为一个双端队列。 + + +## 2、常用方法 + +LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。 +LinkedList 实现 List 接口,能对它进行队列操作。 +LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。 +LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。 +LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。 +LinkedList 是非同步的。 + +LinkedList相对于ArrayList来说,是可以快速添加,删除元素,ArrayList添加删除元素的话需移动数组元素,可能还需要考虑到扩容数组长度。 + +**常用的类接口** + +AbstractSequentialList +``` +AbstractSequentialList类是AbstractList子类,同时也提供了一个基本的list接口的实现,为顺序访问的数据存储结构(链表)提供了最小化的实现。而对于随机访问的数据存储结构(数组)要优先考虑使用AbstractList。 +AbstractSequentiaList是在迭代器基础上实现的get、set、add等方法。 +``` +Deque / Queue 接口 +``` +Deque接口继承Queue接口,两端都允许插入和删除元素,即双向队列。 +``` +List接口 +``` +实现了list接口,实现了get(int location)、remove(int location)等根据索引值来获取、删除节点的函数。 +``` +Cloneable接口 +``` +实现了Cloneable接口,能被克隆。 +``` +Serializable接口 +``` +实现了Serializable接口,支持序列化 +``` + +## 3、常用方法 + +**属性** +属性LinkedList 提供了以下三个成员变量。size,first,last。 +``` +transient int size = 0; + transient Node first; + transient Node last;复制代码其中 size 为 LinkedList 的大小,first和last均是Node类的实例。first指向头结点,last指向的尾节点。Node 为节点对象。Node 是 LInkedList 的内部类,定义了存储的数据元素,前一个节点和后一个节点,典型的双链表结构。 + private static class Node { + E item; //结点的值 + Node next; //结点的后向指针 + Node prev; //结点的前向指针 + + //构造方法中已完成Node成员的赋值 + Node(Node prev, E element, Node next) { + this.item = element; //结点的值赋值为element + this.next = next; //后向指针赋值 + this.prev = prev; //前向指针赋值 + } + } +``` + +**构造函数** +``` +空参构造函数 +public LinkedList() { +} + +public LinkedList(Collection c) { + this(); + addAll(c); //把集合中所有节点添加到list中 +} + +集合构造函数 +public boolean addAll(Collection c) { + return addAll(size, c); +} + +public boolean addAll(int index, Collection c) { + checkPositionIndex(index); //检查下标合法性 + + Object[] a = c.toArray(); //把集合转换为数组 + int numNew = a.length; //添加元素数量 + if (numNew == 0) + return false; + + Node pred, succ; + if (index == size) { //把集合c插入到最后面 + succ = null; + pred = last; + } else { + succ = node(index); //插入中间 + pred = succ.prev; + } + + for (Object o : a) { //循环插入节点 + @SuppressWarnings("unchecked") E e = (E) o; + Node newNode = new Node<>(pred, e, null); + if (pred == null) + first = newNode; + else + pred.next = newNode; + pred = newNode; + } + + if (succ == null) { //如果插入最后面,则last节点是最后一个插入的节点 + last = pred; + } else { + pred.next = succ; + succ.prev = pred; + } + + size += numNew; //list大小加上增加集合的元素数量 + modCount++; //修改次数加1 + return true; +} + +private void checkPositionIndex(int index) { + if (!isPositionIndex(index)) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); +} +private boolean isPositionIndex(int index) { + return index >= 0 && index <= size; //比较index在0到size之间 +} + +/** +* 获取下标为index的节点 +*/ +Node node(int index) { + // assert isElementIndex(index); + + if (index < (size >> 1)) { //如果index在前半部分,则从前循环 + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { //如果index在后半部分,则从后循环 + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } +} + +``` + +**addAll** + +``` +add(E e) 一个参数方法,默认天骄到list最后面 + +public boolean add(E e) { + linkLast(e); + return true; + } + void linkLast(E e) { + final Node l = last; + final Node newNode = new Node<>(l, e, null); //新建node节点,pre指向list中的last节点 + last = newNode; + if (l == null) //如果list为空,新增节点赋值给first + first = newNode; + else + l.next = newNode; //如果不为空,list中的last指向新增节点,完成新增节点动作 + size++; + modCount++; + } + +add(int index, E e),把元素 e 添加到下标index位置 +public void add(int index, E element) { + checkPositionIndex(index); + + if (index == size) //如果index等于size,添加到list最后面 + linkLast(element); + else + linkBefore(element, node(index)); + } + + void linkBefore(E e, Node succ) { + // assert succ != null; + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, e, succ); //插入到当前index上的节点和它的pred节点中间 + succ.prev = newNode; + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + modCount++; + } + +``` +**remove操作** + +``` +remove()删除第一个元素,直接调用的removeFirst()方法 +remove(int index)删除下标index上的元素,如果下边越界,会抛IndexOutOfBoundsException异常 +remove(Object o)删除第一个匹配到的元素o +removeFirst()删除第一个元素(如果list为空,会抛出NoSuchElementException异常) +removeFirstOccurrence(Object o)直接调用remove(o) +removeLast()删除最后一个元素(如果list为空,会抛出NoSuchElementException异常) +removeLastOccurrence(Object o)删除最后一个匹配到的元素 + + +public E remove() { + return removeFirst(); + } + public E removeFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return unlinkFirst(f); + } + private E unlinkFirst(Node f) { + // assert f == first && f != null; + final E element = f.item; + final Node next = f.next; + f.item = null; + f.next = null; // 断开删除节点的连接,帮助垃圾回收 + first = next; + if (next == null) + last = null; + else + next.prev = null; + size--; + modCount++; + return element; + } + + public E remove(int index) { + checkElementIndex(index); + return unlink(node(index)); //删除下标index上的节点 + } + E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next; + final Node prev = x.prev; + + if (prev == null) { //如果是头节点,first指向next节点,即删除当前节点 + first = next; + } else { + prev.next = next; //否则,前一个节点的next指向下一个节点 + x.prev = null; + } + + if (next == null) { //如果是尾节点,last指向前一个节点,即删除当前节点 + last = prev; + } else { + next.prev = prev; //否则,下一个节点的prev指向前一个节点 + x.next = null; + } + + x.item = null; + size--; + modCount++; + return element; + } + + /** + * 删除list中最前面的元素o(如果存在),从first节点往后循环查找 + * null的equals方法总是返回false,所以需要分开判断 + */ + public boolean remove(Object o) { + if (o == null) { + for (Node x = first; x != null; x = x.next) { //从first节点往后循环,删除第一个匹配的元素,结束循环 + if (x.item == null) { + unlink(x); + return true; + } + } + } else { + for (Node x = first; x != null; x = x.next) { + if (o.equals(x.item)) { + unlink(x); + return true; + } + } + } + return false; + } + + public boolean removeFirstOccurrence(Object o) { + return remove(o); + } + + public E removeLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return unlinkLast(l); + } + + public boolean removeLastOccurrence(Object o) { //删除list中最后面的元素o(如果存在),从last节点往前循环查找即可 + if (o == null) { + for (Node x = last; x != null; x = x.prev) { + if (x.item == null) { + unlink(x); + return true; + } + } + } else { + for (Node x = last; x != null; x = x.prev) { + if (o.equals(x.item)) { + unlink(x); + return true; + } + } + } + return false; + } + +``` + +**其他方法** + + +``` +``` + + diff --git a/second/week_02/86/AtomicInteger.md b/second/week_02/86/AtomicInteger.md new file mode 100644 index 0000000000000000000000000000000000000000..fd7f86131cc03dbf02d7e3338bd25057d6659077 --- /dev/null +++ b/second/week_02/86/AtomicInteger.md @@ -0,0 +1,253 @@ +## 1、简介 +AtomicInteger 是一个Java concurrent包提供的一个原子类,通过这个类可以对Integer进行一些原子操作。 + +AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。 + +## 2、常用接口 + +``` +public final int get() //获取当前的值 +public final int getAndSet(int newValue)//获取当前的值,并设置新的值 +public final int getAndIncrement()//获取当前的值,并自增 +public final int getAndDecrement() //获取当前的值,并自减 +public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 +``` + +## 3、常用方法的介绍 +AtomicInteger 的优势 +``` +AtomicInteger +class Test2 { + private AtomicInteger count = new AtomicInteger(); + + public void increment() { + count.incrementAndGet(); + } + //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。 + public int getCount() { + return count.get(); + } +} + +普通线程 +class Test2 { + private volatile int count = 0; + + public synchronized void increment() { + count++; //若要线程安全执行执行count++,需要加锁 + } + + public int getCount() { + return count; + } +} + +从上面的例子中我们可以看出:使用AtomicInteger是非常的安全的.而且因为AtomicInteger由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。 +``` + +``` +package com.collection.test; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 原子类的测试 + */ +public class AtomicTest { + private static AtomicInteger atomicInteger = new AtomicInteger(); + + //获取当前值 + public static void getCurrentValue(){ + System.out.println(atomicInteger.get());//-->0 + } + + //设置value值 + public static void setValue(){ + atomicInteger.set(12);//直接用12覆盖旧值 + System.out.println(atomicInteger.get());//-->12 + } + + //根据方法名称getAndSet就知道先get,则最后返回的就是旧值,如果get在后,就是返回新值 + public static void getAndSet(){ + System.out.println(atomicInteger.getAndSet(15));//-->12 + } + + public static void getAndIncrement(){ + System.out.println(atomicInteger.getAndIncrement());//-->15 + } + + public static void getAndDecrement(){ + System.out.println(atomicInteger.getAndDecrement());//-->16 + } + + public static void getAndAdd(){ + System.out.println(atomicInteger.getAndAdd(10));//-->15 + } + + public static void incrementAndGet(){ + System.out.println(atomicInteger.incrementAndGet());//-->26 + } + + public static void decrementAndGet(){ + System.out.println(atomicInteger.decrementAndGet());//-->25 + } + + public static void addAndGet(){ + System.out.println(atomicInteger.addAndGet(20));//-->45 + } + + public static void main(String[] args) { + AtomicTest test = new AtomicTest(); + test.getCurrentValue(); + test.setValue(); + //返回旧值系列 + test.getAndSet(); + test.getAndIncrement(); + test.getAndDecrement(); + test.getAndAdd(); + //返回新值系列 + test.incrementAndGet(); + test.decrementAndGet(); + test.addAndGet(); + + } +} + +``` + +``` +private volatile int value;// 初始化值 + + /** + * 创建一个AtomicInteger,初始值value为initialValue + */ + public AtomicInteger(int initialValue) { + value = initialValue; + } + + /** + * 创建一个AtomicInteger,初始值value为0 + */ + public AtomicInteger() { + } + + /** + * 返回value + */ + public final int get() { + return value; + } + + /** + * 为value设值(基于value),而其他操作是基于旧值<--get() + */ + public final void set(int newValue) { + value = newValue; + } + + public final boolean compareAndSet(int expect, int update) { + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } + + /** + * 基于CAS为旧值设定新值,采用无限循环,直到设置成功为止 + * + * @return 返回旧值 + */ + public final int getAndSet(int newValue) { + for (;;) { + int current = get();// 获取当前值(旧值) + if (compareAndSet(current, newValue))// CAS新值替代旧值 + return current;// 返回旧值 + } + } + + /** + * 当前值+1,采用无限循环,直到+1成功为止 + * @return the previous value 返回旧值 + */ + public final int getAndIncrement() { + for (;;) { + int current = get();//获取当前值 + int next = current + 1;//当前值+1 + if (compareAndSet(current, next))//基于CAS赋值 + return current; + } + } + + /** + * 当前值-1,采用无限循环,直到-1成功为止 + * @return the previous value 返回旧值 + */ + public final int getAndDecrement() { + for (;;) { + int current = get(); + int next = current - 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * 当前值+delta,采用无限循环,直到+delta成功为止 + * @return the previous value 返回旧值 + */ + public final int getAndAdd(int delta) { + for (;;) { + int current = get(); + int next = current + delta; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * 当前值+1, 采用无限循环,直到+1成功为止 + * @return the updated value 返回新值 + */ + public final int incrementAndGet() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return next;//返回新值 + } + } + + /** + * 当前值-1, 采用无限循环,直到-1成功为止 + * @return the updated value 返回新值 + */ + public final int decrementAndGet() { + for (;;) { + int current = get(); + int next = current - 1; + if (compareAndSet(current, next)) + return next;//返回新值 + } + } + + /** + * 当前值+delta,采用无限循环,直到+delta成功为止 + * @return the updated value 返回新值 + */ + public final int addAndGet(int delta) { + for (;;) { + int current = get(); + int next = current + delta; + if (compareAndSet(current, next)) + return next;//返回新值 + } + } + + /** + * 获取当前值 + */ + public int intValue() { + return get(); + } + +``` + + + diff --git a/second/week_02/86/AtomicStampedreference.md b/second/week_02/86/AtomicStampedreference.md new file mode 100644 index 0000000000000000000000000000000000000000..e2adc54a361bb18786fc0f0e5e52a9b076c545f8 --- /dev/null +++ b/second/week_02/86/AtomicStampedreference.md @@ -0,0 +1,260 @@ +## 1、简介 +``` +``` +AtomicStampedReference 是维护带有整数标识的对象引用,可以用原子方式对它进行更新,能解决无锁 CAS 的 ABA 问题,一般在涉及金额的时使用。 + +高并发场景下,自旋 CAS 长时间失败会导致 CPU 飙升。 + + + + + +## 2、常用方法 + +``` +import java.util.concurrent.atomic.AtomicReference; + +public class ABATest { + static AtomicReference atomicReference = new AtomicReference(1); + + public static void main(String[] args) throws InterruptedException { + + new Thread(new Runnable() { + + @Override + public void run() { + System.out.println(Thread.currentThread()+ "-" + atomicReference.compareAndSet(1, 2)); + System.out.println(Thread.currentThread()+ "-" + atomicReference.compareAndSet(2, 1)); + } + }).start(); + + new Thread(new Runnable() { + + @Override + public void run() { + System.out.println(Thread.currentThread()+ "-" + atomicReference.compareAndSet(1, 2)); + } + }).start(); + + Thread.currentThread().sleep(3000); + System.out.println(atomicReference.get()); + + } +} + +/** +通过static pair保存一个引用和计数器 +*/ +private static class Pair { + final T reference; + final int stamp; + private Pair(T reference, int stamp) { + this.reference = reference; + this.stamp = stamp; + } + static Pair of(T reference, int stamp) { + return new Pair(reference, stamp); + } +} + +private volatile Pair pair; + +/** + * 通过传入的初始化引用和计数器来构造函数一个pair + * + * @param initialRef 初始化用用 + * @param initialStamp 初始化计数器或者叫时间戳 + */ +public AtomicStampedReference(V initialRef, int initialStamp) { + pair = Pair.of(initialRef, initialStamp); +} + +``` + +## 3、常用方法的介绍 + +``` +创建实例 +private static class Pair { + /** + * 目标对象引用 + */ + final T reference; + /** + * 整形标记 + */ + final int stamp; + private Pair(T reference, int stamp) { + this.reference = reference; + this.stamp = stamp; + } + static Pair of(T reference, int stamp) { + return new Pair<>(reference, stamp); + } + } + + private volatile Pair pair; + + /** + * 创建具有给定对象引用和标识值的新 AtomicStampedReference 实例 + */ + public AtomicStampedReference(V initialRef, int initialStamp) { + pair = Pair.of(initialRef, initialStamp); + } +``` + +``` +尝试原子更新 + /** + * 如果旧引用==expectedReference && 旧整形标记==expectedStamp, + * 则尝试原子更新引用为 newReference,时间标记为 newStamp + */ + public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + final Pair current = pair; + return + expectedReference == current.reference && + expectedStamp == current.stamp && + (newReference == current.reference && + newStamp == current.stamp || + casPair(current, Pair.of(newReference, newStamp))); + } +``` + +``` +读取值 +/** + * 读取引用值 + */ + public V getReference() { + return pair.reference; + } + + /** + * 读取整形标记 + */ + public int getStamp() { + return pair.stamp; + } + + /** + * 读取引用值,并将整形标记存储到形参数组索引为 0 的位置 + */ + public V get(int[] stampHolder) { + final Pair pair = this.pair; + stampHolder[0] = pair.stamp; + return pair.reference; + } + +``` + +``` +写入值 + /** + * 无条件地更新引用值和时间标记 + */ + public void set(V newReference, int newStamp) { + final Pair current = pair; + if (newReference != current.reference || newStamp != current.stamp) { + this.pair = Pair.of(newReference, newStamp); + } + } +``` + +``` +原子更新值,并发更新时可能失败 +/** + * 如果旧引用==expectedReference && 旧整形标记==expectedStamp, + * 则尝试原子更新引用为 newReference,时间标记为 newStamp + */ + public boolean weakCompareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + return compareAndSet(expectedReference, newReference, + expectedStamp, newStamp); + } + + /** + * 如果旧引用==expectedReference && 旧整形标记==expectedStamp, + * 则尝试原子更新引用为 newReference,时间标记为 newStamp + */ + public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + final Pair current = pair; + return + expectedReference == current.reference && + expectedStamp == current.stamp && + (newReference == current.reference && + newStamp == current.stamp || + casPair(current, Pair.of(newReference, newStamp))); + } + + private static final VarHandle PAIR; + static { + try { + final MethodHandles.Lookup l = MethodHandles.lookup(); + PAIR = l.findVarHandle(AtomicStampedReference.class, "pair", + Pair.class); + } catch (final ReflectiveOperationException e) { + throw new Error(e); + } + } + + private boolean casPair(Pair cmp, Pair val) { + return AtomicStampedReference.PAIR.compareAndSet(this, cmp, val); + } +``` + +``` +内部类 +private static class Pair { + final T reference;//元素值 + final int stamp;//邮戳(版本号) + private Pair(T reference, int stamp) { + this.reference = reference; + this.stamp = stamp; + } + static Pair of(T reference, int stamp) { + return new Pair(reference, stamp); + } + } +``` + +``` +属性 +//volatile保证Pair的修改对所有线程可见 + private volatile Pair pair; + //unsafe实例 + private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); + //获取pair的偏移量 + private static final long pairOffset = + objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); +``` + +``` +compareAndSet() +public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + //获取当前(元素值 + 版本号)对象 + Pair current = pair; + return + //若元素地址没变 + expectedReference == current.reference && + //版本号没变 + expectedStamp == current.stamp && + //新的元素地址 = 旧的元素地址 + ((newReference == current.reference && + //新的版本号 = 旧的版本号 + newStamp == current.stamp) || + //根据新的元素地址和版本号构造新的Pair并CAS更新 + casPair(current, Pair.of(newReference, newStamp))); + } +``` + diff --git a/second/week_02/86/Unsafe.md b/second/week_02/86/Unsafe.md new file mode 100644 index 0000000000000000000000000000000000000000..5c8fa42e932f9e743aec3f60f8f50dd3f8111ff8 --- /dev/null +++ b/second/week_02/86/Unsafe.md @@ -0,0 +1,330 @@ +## 1、简介 + +Unsafe 是一个位于sun.misc包内的一个类,它可以直接操作堆外内存,可以随意查看、修改JVM中运行时的数据结构(查看和修改对象成员),它的操作粒度不是类,而是数据和地址。 + +Unsafe 的一些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到很大的作用。 + +注:在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,一定要谨慎操作。 + +``` +``` + +## 2、常用方法的作用 + +**获得Unsafe对象** +``` + @CallerSensitive + public static Unsafe getUnsafe() { + Class var0 = Reflection.getCallerClass(); + if(!VM.isSystemDomainLoader(var0.getClassLoader())) { + throw new SecurityException("Unsafe"); + } else { + return theUnsafe; + } + } + 但是很遗憾我们并不能直接调用,因为这个getUnsafe方法会判断当前调用这个方法的对象的类型,如果并非 java.util.concurrent.atomic内的原子类、AbstractQueuedSynchronizer等类型,则会抛出SecurityException不安全异常。因此需要反射来调用这个方法从而获得Unsafe对象实例: + +public static Unsafe getUnsafe() { + try { + Field singletonInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); + singletonInstanceField.setAccessible(true); + return (Unsafe) singletonInstanceField.get(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } +``` + +``` +``` + +## 3、常用方法的介绍 + +通过内存偏移量原子性的更新成员变量的值 +``` +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +/** + * Description: + * + * @author zhiminxu + * @package com.lordx.sprintbootdemo + * @create_time 2019-03-22 + */ +public class UnsafeTest { + + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException { + Test test = new Test(); + test.test(); + } +} + +class Test { + + private int count = 0; + + public void test() throws NoSuchFieldException, IllegalAccessException { + // 获取unsafe实例 + Class klass = Unsafe.class; + Field field = klass.getDeclaredField("theUnsafe"); + field.setAccessible(true); + Unsafe unsafe = (Unsafe) field.get(null); + + // 获取count域的Field + Class unsafeTestClass = Test.class; + Field fieldCount = unsafeTestClass.getDeclaredField("count"); + fieldCount.setAccessible(true); + + // 计算count的内存偏移量 + long countOffset = (int) unsafe.objectFieldOffset(fieldCount); + System.out.println(countOffset); + + // 原子性的更新指定偏移量的值(将count的值修改为3) + unsafe.compareAndSwapInt(this, countOffset, count, 3); + + // 获取指定偏移量的int值 + System.out.println(unsafe.getInt(this, countOffset)); + } +} +``` + +``` +用 Unsafe 模拟 synchronized +用到了Unsafe中的monitorEnter和monitorExit方法,但monitorEnter后一定要记着monitorExit。 +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +/** + * Description: + * + * @author zhiminxu + * @package com.lordx.sprintbootdemo + * @create_time 2019-03-22 + */ +public class UnsafeTest { + + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException { + final Test test = new Test(); + // 模拟两个线程并发给Test.count递增的场景 + new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 1000000; i++) { + test.addCount(); + } + } + }).start(); + new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 1000000; i++) { + test.addCount(); + } + } + }).start(); + Thread.sleep(5000); + System.out.println(test.getCount()); + } +} + +class Test { + + private int count = 0; + + public int getCount() { + return this.count; + } + + private Unsafe unsafe; + public Test() { + try { + Class klass = Unsafe.class; + Field field = klass.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = (Unsafe) field.get(null); + }catch (Exception e) { + e.printStackTrace(); + } + } + + private Object lock = new Object(); + + public void addCount() { + // 给lock对象设置锁 + unsafe.monitorEnter(lock); + count++; + // 给lock对象解锁 + unsafe.monitorExit(lock); + } + + +} +``` + +``` +compareAndSwapInt +/** + * Atomically update Java variable to x if it is currently + * holding expected. + * 如果对象o指定offset所持有的值是expected,那么将它原子性的改为值x。 + * @return true if successful + */ +public final native boolean compareAndSwapInt(Object o, long offset, + int expected, + int x); + +UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) + UnsafeWrapper("Unsafe_CompareAndSwapInt"); + // #1 + oop p = JNIHandles::resolve(obj); + // #2 + jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); + // #3 + return (jint)(Atomic::cmpxchg(x, addr, e)) == e; +UNSAFE_END + + +``` + +``` +getAndAddInt +/** + * Atomically adds the given value to the current value of a field + * or array element within the given object o + * at the given offset. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param delta the value to add + * @return the previous value + * @since 1.8 + */ +public final int getAndAddInt(Object o, long offset, int delta) { + int v; + do { + v = getIntVolatile(o, offset); + } while (!compareAndSwapInt(o, offset, v, v + delta)); + return v; +} +用于原子性的将值delta加到对象o的offset上。 +getIntVolatile方法用于获取对象o指定偏移量的int值,此操作具有volatile内存语义,也就是说,即使对象o指定offset的变量不是volatile的,次操作也会使用volatile语义,会强制从主存获取值。 +然后通过compareAndSwapInt来替换值,直到替换成功后,退出循环。 + +``` +``` +直接操作内存 +  使用直接Unsafe分配和释放内存以及写和读。通过下面方式获得Unsafe实例: + +private static Unsafe getUnsafe() throws Exception { +// Get the Unsafe object instance +Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); +field.setAccessible(true); +return (sun.misc.Unsafe) field.get(null); +} + +直接从内存读写, 通过其方法allocateMemory获得内存地址,通过putAddress 或putByte 写内存即可: + +public static void showBytes() { + try { + Unsafe unsafe = getUnsafe(); + + // Writing to a memory - MAX VALUE Byte + byte value = Byte.MAX_VALUE; + long bytes = 1; + // Allocate given memory size + long memoryAddress = unsafe.allocateMemory(bytes); + // Write value to the allocated memory + unsafe.putAddress(memoryAddress, value); // or putByte + + // Output the value written and the memory address + System.out.println("[Byte] Writing " + value + " under the " + memoryAddress + " address."); + + long readValue = unsafe.getAddress(memoryAddress); // or getByte + + // Output the value from + System.out.println("[Byte] Reading " + readValue + " from the " + memoryAddress + " address."); + + // C style! Release the Kraken... Memory!! :) + unsafe.freeMemory(memoryAddress); + + } catch (Exception e) { + e.printStackTrace(); + } + } + +从内存直接写一个Long值: + +private static void showLong() { + try { + Unsafe unsafe = getUnsafe(); + + // Writing to a memory - MAX VALUE of Long + long value = Long.MAX_VALUE; + long bytes = Long.SIZE; + // Allocate given memory size + long memoryAddress = unsafe.allocateMemory(bytes); + // Write value to the allocated memory + unsafe.putLong(memoryAddress, value); + + // Output the value written and the memory address + System.out.println("[Long] Writing " + value + " under the " + memoryAddress + " address."); + + // Read the value from the memory + long readValue = unsafe.getLong(memoryAddress); + + // Output the value from + System.out.println("[Long] Reading " + readValue + " from the " + memoryAddress + " address."); + + // C style! Release the Kraken... Memory!! :) + unsafe.freeMemory(memoryAddress); + + } catch (Exception e) { + e.printStackTrace(); + } + } + +重新分配内存: + +public static void showDontFreeMemory() { + for (int t = 0; t < 100; t++) { + new Thread() { + public void run() { + System.out.println("Thread " + Thread.currentThread().getName() + " start!"); + for (int i = 0; i < 1000000; i++) { + try { + Unsafe unsafe = getUnsafe(); + + // Writing random Long to a memory + long value = new Random().nextLong(); + long bytes = Long.SIZE; + // Allocate given memory size + long memoryAddress = unsafe.allocateMemory(bytes); + // Write value to the allocated memory + unsafe.putLong(memoryAddress, value); + + // Read the value from the memory + long readValue = unsafe.getLong(memoryAddress); + + // Always free the memory !! + // ... FIXME: deallocate the memory used + + } catch (Exception e) { + e.printStackTrace(); + } + } + + System.out.println("Thread " + Thread.currentThread().getName() + " stop!"); + }; + + }.start(); + } + } +``` + +