From d68ace5efe1e747e46b46a51059510f1e4c15823 Mon Sep 17 00:00:00 2001 From: zain Date: Sun, 15 Mar 2020 22:03:52 +0800 Subject: [PATCH] week 02 --- second/week_02/87/ABA.java | 78 ++++++++ ...AtomicInteger-\345\255\246\344\271\240.md" | 110 +++++++++++ second/week_02/87/AtomicIntegerTest.java | 39 ++++ ...mpedReference-\345\255\246\344\271\240.md" | 93 ++++++++++ .../JDK-Unsafe-\345\255\246\344\271\240.md" | 172 ++++++++++++++++++ second/week_02/87/Main.java | 12 ++ second/week_02/87/MyObj.java | 27 +++ second/week_02/87/MyObjChild.java | 95 ++++++++++ 8 files changed, 626 insertions(+) create mode 100644 second/week_02/87/ABA.java create mode 100644 "second/week_02/87/AtomicInteger-\345\255\246\344\271\240.md" create mode 100644 second/week_02/87/AtomicIntegerTest.java create mode 100644 "second/week_02/87/AtomicStampedReference-\345\255\246\344\271\240.md" create mode 100644 "second/week_02/87/JDK-Unsafe-\345\255\246\344\271\240.md" create mode 100644 second/week_02/87/Main.java create mode 100644 second/week_02/87/MyObj.java create mode 100644 second/week_02/87/MyObjChild.java diff --git a/second/week_02/87/ABA.java b/second/week_02/87/ABA.java new file mode 100644 index 0000000..82477e7 --- /dev/null +++ b/second/week_02/87/ABA.java @@ -0,0 +1,78 @@ +package cn.jomoon.week2; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicStampedReference; + + +public class ABA { + + private static AtomicInteger atomicInt = new AtomicInteger(100); + private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0); + + public static void main(String[] args) throws InterruptedException { + // 线程1 是 100 -> 101 ,101 - >100 + Thread intT1 = new Thread(new Runnable() { + public void run() { + atomicInt.compareAndSet(100, 101); + atomicInt.compareAndSet(101, 100); + } + }); + + Thread intT2 = new Thread(new Runnable() { + + public void run() { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + boolean c3 = atomicInt.compareAndSet(100, 101); + // 能修改成功 察觉不到 ABA的变化情况 + System.out.println(c3); // true + } + }); + + intT1.start(); + intT2.start(); + intT1.join(); + intT2.join(); + + Thread refT1 = new Thread(new Runnable() { + + public void run() { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); + atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); + } + }); + + Thread refT2 = new Thread(new Runnable() { + + public void run() { + int stamp = atomicStampedRef.getStamp(); + // stamp = 0 + System.out.println("before sleep : stamp = " + stamp); + try { + TimeUnit.SECONDS.sleep(2); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // stamp = 2 + System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp()); + // 通过版本号 查看出来不是同一个版本不能修改成功 + // 乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题 + boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1); + System.out.println(c3); // false + } + }); + + refT1.start(); + refT2.start(); + } + +} diff --git "a/second/week_02/87/AtomicInteger-\345\255\246\344\271\240.md" "b/second/week_02/87/AtomicInteger-\345\255\246\344\271\240.md" new file mode 100644 index 0000000..1288590 --- /dev/null +++ "b/second/week_02/87/AtomicInteger-\345\255\246\344\271\240.md" @@ -0,0 +1,110 @@ +# AtomicInteger + +## 主要的成员变量 + +```java + // unsafe对象 + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + // 加载对应的属性的偏移量 + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + // 存的value值 + private volatile int value; +``` + + + +## lazySet + +```cpp +1.首先set()是对volatile变量的一个写操作, 我们知道volatile的write为了保证对其他线程的可见性会追加以下两个Fence(内存屏障) +1)StoreStore // 在intel cpu中, 不存在[写写]重排序, 这个可以直接省略了 +2)StoreLoad // 这个是所有内存屏障里最耗性能的 +注: 内存屏障相关参考Doug Lea大大的cookbook (http://g.oswego.edu/dl/jmm/cookbook.html) + +2.Doug Lea大大又说了, lazySet()省去了StoreLoad屏障, 只留下StoreStore +``` + + + +## incrementAndGet + +```csharp +public final int incrementAndGet() { + return unsafe.getAndAddInt(this, valueOffset, 1) + 1; +} +``` + +```java +public final int getAndAddInt(Object var1, long var2, int var4) { + int var5; + do { + // 直接从该对象对应位置取出变量的值 + var5 = this.getIntVolatile(var1, var2); + // 通过cas来做 + } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); + return var5; +} +``` + +为什么要返回值也是不太明白。 + +cas在并发量大的时候,由于真正更新成功的线程少,循环次数过多,浪费时间。 + + + +### getAndUpdate + +```java +public final int getAndUpdate(IntUnaryOperator updateFunction) { + int prev, next; + do { + prev = get(); + next = updateFunction.applyAsInt(prev); + } while (!compareAndSet(prev, next)); + return prev; +} +``` + + + +### __cmpxchg + +```cpp +/* + * 根据size大小比较交换字节,字或者双字,如果返回old则交换成功,否则交换失败 + */ +static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old, + unsigned long new, int size) +{ + unsigned long prev; + switch (size) { + case 1: + __asm__ __volatile__(LOCK_PREFIX "cmpxchgb %b1,%2" + : "=a"(prev) + : "q"(new), "m"(*__xg(ptr)), "0"(old) + : "memory"); + return prev; + case 2: + __asm__ __volatile__(LOCK_PREFIX "cmpxchgw %w1,%2" + : "=a"(prev) + : "r"(new), "m"(*__xg(ptr)), "0"(old) + : "memory"); + return prev; +//eax = old,比较%2 = ptr->counter和eax是否相等,如果相等则ZF置位,并把%1 = new赋值给ptr->counter,返回old值,否则ZF清除,并且将ptr->counter赋值给eax + case 4: + __asm__ __volatile__(LOCK_PREFIX "cmpxchgl %1,%2" + : "=a"(prev) + : "r"(new), "m"(*__xg(ptr)), "0"(old) //0表示eax = old + : "memory"); + return prev; + } + return old; +} +``` \ No newline at end of file diff --git a/second/week_02/87/AtomicIntegerTest.java b/second/week_02/87/AtomicIntegerTest.java new file mode 100644 index 0000000..82e5cd6 --- /dev/null +++ b/second/week_02/87/AtomicIntegerTest.java @@ -0,0 +1,39 @@ +package cn.jomoon.week2; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + + +public class AtomicIntegerTest { + private static final int THREADS_COUNT = 2; + + public static int count = 0; + public static volatile int countVolatile = 0; + public static AtomicInteger atomicInteger = new AtomicInteger(0); + public static CountDownLatch countDownLatch = new CountDownLatch(2); + + public static void increase() { + count++; + countVolatile++; + atomicInteger.incrementAndGet(); + } + + public static void main(String[] args) throws InterruptedException { + Thread[] threads = new Thread[THREADS_COUNT]; + for (int i = 0; i < threads.length; i++) { + threads[i] = new Thread(() -> { + for (int i1 = 0; i1 < 1000; i1++) { + increase(); + } + countDownLatch.countDown(); + }); + threads[i].start(); + } + + countDownLatch.await(); + + System.out.println(count); + System.out.println(countVolatile); + System.out.println(atomicInteger.get()); + } +} diff --git "a/second/week_02/87/AtomicStampedReference-\345\255\246\344\271\240.md" "b/second/week_02/87/AtomicStampedReference-\345\255\246\344\271\240.md" new file mode 100644 index 0000000..c472dee --- /dev/null +++ "b/second/week_02/87/AtomicStampedReference-\345\255\246\344\271\240.md" @@ -0,0 +1,93 @@ +# AtomicStampedReference + +> ABA问题用链表去解释会比较清晰。 +> +> 实际上就是通过version来做控制 + + + +## 成员变量 + +```java + 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; + + private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); + private static final long pairOffset = + objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); +``` + + + +### 构造函数 + +```java +public AtomicStampedReference(V initialRef, int initialStamp) { + pair = Pair.of(initialRef, initialStamp); +} +``` + +## 常用方法 + +```java +//构造方法, 传入引用和戳 +public AtomicStampedReference(V initialRef, int initialStamp) +//返回引用 +public V getReference() +//返回版本戳 +public int getStamp() +//如果当前引用 等于 预期值&&当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存 +public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) +//如果当前引用 等于 预期引用, 将更新新的版本戳到内存 +public boolean attemptStamp(V expectedReference, int newStamp) +//设置当前引用的新引用和版本戳 +public void set(V newReference, int newStamp) +``` + + + +### 主要使用的方法 + +```java +private boolean casPair(Pair cmp, Pair val) { + return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); +} +``` + +### 其他方法 + +```java +public V get(int[] stampHolder) { + Pair pair = this.pair; + stampHolder[0] = pair.stamp; + return pair.reference; +} +``` + +### weakCompareAndSet + +```java + public boolean weakCompareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + return compareAndSet(expectedReference, newReference, + expectedStamp, newStamp); + } +``` + +此方法跟compareAndSet有什么区别? \ No newline at end of file diff --git "a/second/week_02/87/JDK-Unsafe-\345\255\246\344\271\240.md" "b/second/week_02/87/JDK-Unsafe-\345\255\246\344\271\240.md" new file mode 100644 index 0000000..16aa221 --- /dev/null +++ "b/second/week_02/87/JDK-Unsafe-\345\255\246\344\271\240.md" @@ -0,0 +1,172 @@ +# JDK Unsafe + +> [查看学习文章](https://my.oschina.net/editorial-story/blog/3019773) + +## 功能 + +- 绕过JVM直接修改内存 +- 使用硬件CPU指令实现CAS原子操作 + + + +返回的Unsafe对象可以读写任意的内存地址数据,一定不用把它传递到非信任代码。 + +既然通过反射获取unsafe对象是违背单例原则的,那么应该如何去获取到这个对象呢? + + + +```java +public final class Unsafe { + + private static native void registerNatives(); + static { + // + registerNatives(); + sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); + } + // 单例设计 + private Unsafe() {} + + private static final Unsafe theUnsafe = new Unsafe(); + + // 获取单例 调用者敏感的 + @CallerSensitive + public static Unsafe getUnsafe() { + Class caller = Reflection.getCallerClass(); + if (!VM.isSystemDomainLoader(caller.getClassLoader())) + throw new SecurityException("Unsafe"); + return theUnsafe; + } + ...... +} +``` + +获取变量值符合条件的用法: + +1. offset通过objectFieldOffset方法获取的类的某一字段偏移量,并且关联的对象o是该类的兼容对象(对象o所在的类必须是该类或该类的子类) +2. offset通过staticFieldOffset方法来获取类的某一字断偏移量,o是通过staticFieldBase方法获得的对象 +3. 如果o引用的是数组,则offset的值为B + N * S,其中N是数组的合法下标,B是通过arrayBaseOffset方法从数组类获取的,S是通过arrayIndexScale方法从数组类获取的 + + + +### 测试获取的方法 + + + + + + + +### 测试设置的方法 + +`public native void putInt(Object o, long offset, int x);` + +1. 第一个参数会被解释成 Java 变量(字段或数组)的引用 +2. 参数给定的值会被存储到该变量,变量的类型必须和方法参数的类型一致 +3. 参数 o 是变量关联的 Java 堆对象,可以为 null +4. 参数 offset 代表该变量在该对象的位置,如果 o 是 null 则是内存的绝对地址 + + + +### 操作本地内存 + + + +```java + ///包装malloc,realloc,free + + /** + * 分配指定大小的一块本地内存。分配的这块内存不会初始化,它们的内容通常是没用的数据 + * 返回的本地指针不会是 0,并且该内存块是连续的。调用 freeMemory 方法可以释放此内存,调用 + * reallocateMemory 方法可以重新分配 + */ + public native long allocateMemory(long bytes); + + /** + * 重新分配一块指定大小的本地内存,超出老内存块的字节不会被初始化,它们的内容通常是没用的数据 + * 当且仅当请求的大小为 0 时,该方法返回的本地指针会是 0。 + * 该内存块是连续的。调用 freeMemory 方法可以释放此内存,调用 reallocateMemory 方法可以重新分配 + * 参数 address 可以是 null,这种情况下会分配新内存(和 allocateMemory 一样) + */ + public native long reallocateMemory(long address, long bytes); + + + /** + * 将给定的内存块的所有字节设置成固定的值(通常是 0) + * 该方法通过两个参数确定内存块的基准地址,就像在 getInt(Object,long) 中讨论的,它提供了 double-register 地址模型 + * 如果引用的对象是 null, 则 offset 会被当成绝对基准地址 + * 该写入操作是按单元写入的,单元的字节大小由地址和长度参数决定,每个单元的写入是原子性的。如果地址和长度都是 8 的倍数,则一个单元为 long + * 型(一个单元 8 个字节);如果地址和长度都是 4 的倍数,则一个单元为 int 型(一个单元 4 个字节); + * 如果地址和长度都是 2 的倍数,则一个单元为 short 型(一个单元 2 个字节); + */ + + public native void setMemory(Object o, long offset, long bytes, byte value); + + + //将给定的内存块的所有字节设置成固定的值(通常是 0) + //就像在 getInt(Object,long) 中讨论的,该方法提供 single-register 地址模型 + + public void setMemory(long address, long bytes, byte value) { + setMemory(null, address, bytes, value); + } + + + //复制指定内存块的字节到另一内存块 + //该方法的两个基准地址分别由两个参数决定 + + public native void copyMemory(Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes); + + //复制指定内存块的字节到另一内存块,但使用 single-register 地址模型 + + public void copyMemory(long srcAddress, long destAddress, long bytes) { + copyMemory(null, srcAddress, null, destAddress, bytes); + } + + //释放通过 allocateMemory 或者 reallocateMemory 获取的内存,如果参数 address 是 null,则不做任何处理 + + public native void freeMemory(long address); +``` + +1. malloc 用于分配一个全新未使用的连续内存,但该内存不会初始化,即不会被清零 +2. realloc 用于内存的缩容和扩容,有两个参数,从malloc返回的地址和要调整的大小,该函数和malloc一样,不会初始化,他能保留之前放到内存里的值,很适合扩容。 +3. free 用于释放内存,该方法只有一个地址参数,那它如何知道要释放多少个字节呢?其实在 malloc 分配内存的时候会多分配 4 个字节用于存放该块的长度,比如 malloc(10) 其实会花费 14 个字节。理论上讲能分配的最大内存是 4G(2^32-1)。在 hotspot 虚拟机的设计中,数组对象也有 4 个字节用于存放数组长度,那么在 hotspot 中,数组的最大长度就是 2^32-1,这样 free 函数只要读取前 4 个字节就知道要释放多少内存了(10+4); +4. memset 一般用于初始化内存,可以设置初始化内存的值,一般初始值会设置成 0,即清零操作。 + + + +### 最重要的CAS方法 + +```java + /** + * Atomically update Java variable to x if it is currently + * holding expected. + * @return true if successful + * + * 如果变量的值为预期值,则更新变量的值,该操作为原子操作 + * 如果修改成功则返回true + */ + public final native boolean compareAndSwapObject(Object o, long offset, + Object expected, + Object x); + + /** + * Atomically update Java variable to x if it is currently + * holding expected. + * @return true if successful + */ + public final native boolean compareAndSwapInt(Object o, long offset, + int expected, + int x); + + /** + * Atomically update Java variable to x if it is currently + * holding expected. + * @return true if successful + */ + public final native boolean compareAndSwapLong(Object o, long offset, + long expected, + long x); +``` + diff --git a/second/week_02/87/Main.java b/second/week_02/87/Main.java new file mode 100644 index 0000000..474696f --- /dev/null +++ b/second/week_02/87/Main.java @@ -0,0 +1,12 @@ +package cn.jomoon.week2; + + +public class Main { + public static void main(String[] args) throws NoSuchFieldException { + MyObjChild myObjChild = new MyObjChild(); + // myObjChild.getObjFieldVal(); +// myObjChild.getClsFieldVal(); +// myObjChild.getArrayVal(0,10); + myObjChild.setObjFieldVal(10); + } +} diff --git a/second/week_02/87/MyObj.java b/second/week_02/87/MyObj.java new file mode 100644 index 0000000..e8385c9 --- /dev/null +++ b/second/week_02/87/MyObj.java @@ -0,0 +1,27 @@ +package cn.jomoon.week2; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + + +public class MyObj { + int objField=Integer.MAX_VALUE; + static int clsField=10; + int[] array={}; + static Unsafe U; + static { + try { + init(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + public static void init() throws NoSuchFieldException, IllegalAccessException { + Field f= Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + U= (Unsafe) f.get(null); + } +} diff --git a/second/week_02/87/MyObjChild.java b/second/week_02/87/MyObjChild.java new file mode 100644 index 0000000..ff49a54 --- /dev/null +++ b/second/week_02/87/MyObjChild.java @@ -0,0 +1,95 @@ +package cn.jomoon.week2; + +import java.lang.reflect.Field; + + +public class MyObjChild extends MyObj { + int another; + + public static void getObjFieldVal() throws NoSuchFieldException { + Field field = MyObj.class.getDeclaredField("objField"); + // 非对象字段偏移量 + long offset = U.objectFieldOffset(field); + MyObj obj = new MyObj(); + // 通过偏移量获取int值 + int val = U.getInt(obj, offset); + System.out.println("1.\t" + (val == obj.objField)); + + MyObjChild child = new MyObjChild(); + int corVal1 = U.getInt(child, offset); + // 父亲类和子类,在父类的属性的偏移量是一致的 说明在虚拟机中子类的实例的内存结构**继承了父类的实例的内存结构**。 + System.out.println("2.\t" + (corVal1 == obj.objField)); + // 这里有点问题吧: MyObjChild.class.get... 但如此会包NoSuchFiledException + // 实际上这个应该结合2来看就能理解了 因为2可以间接表明 子类继承了父类的实例的内存结构 + Field fieldChild = MyObj.class.getDeclaredField("objField"); + long offsetChild = U.objectFieldOffset(fieldChild); + System.out.println("3.\t" + (offset == offsetChild)); + + int corVal2 = U.getInt(obj, offsetChild); + System.out.println("4.\t" + (corVal2 == obj.objField)); + + // 如果将数值超过范围的话 这个就会为false + // Intel 处理器的机器的内存中多字节类型是按小端存储 所以如果字段小是可以为true的,如 ARM 支持小端和大端 + short errVal1 = U.getShort(obj, offset); + System.out.println("5.\t" + (errVal1 == obj.objField)); + // 这个想表达什么意思呢? 测试获取非 MyObj 实例的偏移位置的值,这种情况下代码本身并不会报错,但获取到的值并非该字段的值(未定义的值) + int errVal2 = U.getInt("abcd", offset); + System.out.println("6.\t" + errVal2); + + } + + + public static void getClsFieldVal() throws NoSuchFieldException { + // 获取静态字段10 + Field field = MyObj.class.getDeclaredField("clsField"); + long offset = U.staticFieldOffset(field); + // 获取静态字段的所在位置的对象。 获取该静态字段所在的“对象”可通过类似 getInt(Object,long)的方法访问 + Object obj = U.staticFieldBase(field); + int val1 = U.getInt(MyObj.class, offset); + System.out.println("1.\t" + (val1 == 10)); + // 从对象中获取指定偏移量的数据 + int val2 = U.getInt(obj, offset); + System.out.println("2.\t" + (val2 == 10)); + + } + + + public static void getArrayVal(int index, int expectedVal) throws NoSuchFieldException { + // 返回给定数组类第一个元素在内存中的偏移量。如果 arrayIndexScale 方法返回非 0 值,要获得访问数组元素的新的偏移量,则需要使用。 + int base = U.arrayBaseOffset(int[].class); + // 返回给定数组类的每个元素在内存中的 scale(所占用的字节) 每个元素所占用的空间。 + int scale = U.arrayIndexScale(int[].class); + MyObj obj = new MyObj(); + Field field = MyObj.class.getDeclaredField("array"); + long offset = U.objectFieldOffset(field); + int[] array = (int[]) U.getObject(obj, offset); + // 基础偏移量 + index * 每个元素所占用的空间 + int val1 = U.getInt(array, (long) base + index * scale); + System.out.println("1.\t" + (val1 == expectedVal)); + int val2 = U.getInt(obj.array, (long) base + index * scale); + System.out.println("2.\t" + (val2 == expectedVal)); + } + + + public static void setObjFieldVal(int val) throws NoSuchFieldException { + Field field = MyObj.class.getDeclaredField("objField"); + // 获取偏移量 + long offset = U.objectFieldOffset(field); + MyObj obj = new MyObj(); + // 将偏移量的值设置入 + U.putInt(obj, offset, val); + int getVal = U.getInt(obj, offset); + System.out.println(val == getVal); + U.putLong(obj, offset, val); + getVal = U.getInt(obj, offset); + System.out.println(val == getVal); +// Field fieldArray = MyObj.class.getDeclaredField("array"); +// long offsetArray = U.objectFieldOffset(fieldArray); +// int[] array = (int[]) U.getObject(obj, offsetArray); +// for (int i = 0; i < array.length; i++) { +// System.out.println(array[i]); +// } + + } + +} -- Gitee