From 716aef5f70da36ac3ac2e5a4634e6b1ee93df1db Mon Sep 17 00:00:00 2001 From: messi-tju Date: Sun, 15 Mar 2020 23:16:44 +0800 Subject: [PATCH] submits the homework for Week 2 --- second/week_02/79/AtomicInteger.md | 317 ++++++++++++ second/week_02/79/AtomicStampedReference.md | 205 ++++++++ second/week_02/79/Unsafe.md | 509 ++++++++++++++++++++ 3 files changed, 1031 insertions(+) create mode 100644 second/week_02/79/AtomicInteger.md create mode 100644 second/week_02/79/AtomicStampedReference.md create mode 100644 second/week_02/79/Unsafe.md diff --git a/second/week_02/79/AtomicInteger.md b/second/week_02/79/AtomicInteger.md new file mode 100644 index 0000000..e818b5c --- /dev/null +++ b/second/week_02/79/AtomicInteger.md @@ -0,0 +1,317 @@ +### [Java 8] AtomicInteger + +完全利用Unsafe的CAS操作封装了int值的读取-修改-更新原子操作 + +``` java +public class AtomicInteger extends Number implements java.io.Serializable { + private static final long serialVersionUID = 6214790243416807050L; + + // setup to use Unsafe.compareAndSwapInt for updates + // 因为在rt.jar包中,由启动类加载器加载,因此可以直接由getUnsafe()获取到Unsafe实例 + private static final Unsafe unsafe = Unsafe.getUnsafe(); + // 记录实例字段value在AtomicInteger实例中的地址偏移,在静态构造器中初始化,缓存好用于Unsafe的CAS操作 + private static final long valueOffset; + + static { + try { + // 获取实例字段value在AtomicInteger实例中的地址偏移并缓存 + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + // AtomicInteger维护的int值 + // volatile 关键字保证读取value时从主内存刷新到工作内存,写入时从工作内存同步到主内存,保证多线程可见性 + private volatile int value; + + /** + * Creates a new AtomicInteger with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicInteger(int initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicInteger with initial value {@code 0}. + */ + public AtomicInteger() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final int get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + // 无条件更新value值 + public final void set(int newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int newValue) { + // 设置value值。相比直接赋值(volatile写语义),Unsafe的putOrderedInt方法没有使用相对昂贵的StoreLoad语义屏障,在一些value更新不需要立即可见的场景下可以提升性能。但最终可见还是要靠value的volatile写触发 + unsafe.putOrderedInt(this, valueOffset, newValue); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + // 原子进行两个操作:读取值 及 无条件设置新值,即读的值一定是被此次设置覆盖的那个值 + public final int getAndSet(int newValue) { + // 设置新值并返回被替换的值 + return unsafe.getAndSetInt(this, valueOffset, newValue); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return {@code true} if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int expect, int update) { + // 原子比较-替换操作,如果value值为expect则用update替换value值并返回true(替换成功),否则不替换并返回false(替换失败) + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail + * spuriously and does not provide ordering guarantees, so is + * only rarely an appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return {@code true} if successful + */ + // 在OpenJDK 8中和compareAndSet一样 + // 在之后的版本会实现其注释所说的效果:在某些CPU架构中会比compareAndSwapInt有更好的性能,但有可能发生虚假失败(当前value值和expect值一样但还是有可能返回false)以及不保证有序性(CAS与前后指令乱序) + public final boolean weakCompareAndSet(int expect, int update) { + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @return the previous value + */ + public final int getAndIncrement() { + // 原子value++ + return unsafe.getAndAddInt(this, valueOffset, 1); + } + + /** + * Atomically decrements by one the current value. + * + * @return the previous value + */ + public final int getAndDecrement() { + // 原子value-- + return unsafe.getAndAddInt(this, valueOffset, -1); + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + public final int getAndAdd(int delta) { + // 原子value+delta,返回被替换的value值 + return unsafe.getAndAddInt(this, valueOffset, delta); + } + + /** + * Atomically increments by one the current value. + * + * @return the updated value + */ + public final int incrementAndGet() { + // 原子++value + return unsafe.getAndAddInt(this, valueOffset, 1) + 1; + } + + /** + * Atomically decrements by one the current value. + * + * @return the updated value + */ + public final int decrementAndGet() { + // 原子--value + return unsafe.getAndAddInt(this, valueOffset, -1) - 1; + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + public final int addAndGet(int delta) { + // 原子value+delta,返回替换后的value值 + return unsafe.getAndAddInt(this, valueOffset, delta) + delta; + } + + /** + * Atomically updates the current value with the results of + * applying the given function, returning the previous value. The + * function should be side-effect-free, since it may be re-applied + * when attempted updates fail due to contention among threads. + * + * @param updateFunction a side-effect-free function + * @return the previous value + * @since 1.8 + */ + // 原子value+updateFunction(value),返回被替换的value值 + public final int getAndUpdate(IntUnaryOperator updateFunction) { + int prev, next; + do { + prev = get(); + // 由于可能会有多个线程竞争修改,因此循环可能执行多次,故需要保证updateFunction是没有副作用的纯函数 + next = updateFunction.applyAsInt(prev); + } while (!compareAndSet(prev, next)); + return prev; + } + + /** + * Atomically updates the current value with the results of + * applying the given function, returning the updated value. The + * function should be side-effect-free, since it may be re-applied + * when attempted updates fail due to contention among threads. + * + * @param updateFunction a side-effect-free function + * @return the updated value + * @since 1.8 + */ + // 原子value+updateFunction(value),返回替换后的value值 + public final int updateAndGet(IntUnaryOperator updateFunction) { + int prev, next; + do { + prev = get(); + // 由于可能会有多个线程竞争修改,因此循环可能执行多次,故需要保证updateFunction是没有副作用的纯函数 + next = updateFunction.applyAsInt(prev); + } while (!compareAndSet(prev, next)); + return next; + } + + /** + * Atomically updates the current value with the results of + * applying the given function to the current and given values, + * returning the previous value. The function should be + * side-effect-free, since it may be re-applied when attempted + * updates fail due to contention among threads. The function + * is applied with the current value as its first argument, + * and the given update as the second argument. + * + * @param x the update value + * @param accumulatorFunction a side-effect-free function of two arguments + * @return the previous value + * @since 1.8 + */ + // 原子value+accumulatorFunction(value, x),返回被替换的value值 + public final int getAndAccumulate(int x, + IntBinaryOperator accumulatorFunction) { + int prev, next; + do { + prev = get(); + // 由于可能会有多个线程竞争修改,因此循环可能执行多次,故需要保证accumulatorFunction是没有副作用的纯函数 + next = accumulatorFunction.applyAsInt(prev, x); + } while (!compareAndSet(prev, next)); + return prev; + } + + /** + * Atomically updates the current value with the results of + * applying the given function to the current and given values, + * returning the updated value. The function should be + * side-effect-free, since it may be re-applied when attempted + * updates fail due to contention among threads. The function + * is applied with the current value as its first argument, + * and the given update as the second argument. + * + * @param x the update value + * @param accumulatorFunction a side-effect-free function of two arguments + * @return the updated value + * @since 1.8 + */ + // 原子value+accumulatorFunction(value, x),返回替换后的value值 + public final int accumulateAndGet(int x, + IntBinaryOperator accumulatorFunction) { + int prev, next; + do { + prev = get(); + // 由于可能会有多个线程竞争修改,因此循环可能执行多次,故需要保证accumulatorFunction是没有副作用的纯函数 + next = accumulatorFunction.applyAsInt(prev, x); + } while (!compareAndSet(prev, next)); + return next; + } + + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value + */ + public String toString() { + return Integer.toString(get()); + } + + // 以下是Number接口实现,分别将value值转换为各数值基本类型 + + /** + * Returns the value of this {@code AtomicInteger} as an {@code int}. + */ + public int intValue() { + return get(); + } + + /** + * Returns the value of this {@code AtomicInteger} as a {@code long} + * after a widening primitive conversion. + * @jls 5.1.2 Widening Primitive Conversions + */ + public long longValue() { + return (long)get(); + } + + /** + * Returns the value of this {@code AtomicInteger} as a {@code float} + * after a widening primitive conversion. + * @jls 5.1.2 Widening Primitive Conversions + */ + public float floatValue() { + return (float)get(); + } + + /** + * Returns the value of this {@code AtomicInteger} as a {@code double} + * after a widening primitive conversion. + * @jls 5.1.2 Widening Primitive Conversions + */ + public double doubleValue() { + return (double)get(); + } +} +``` diff --git a/second/week_02/79/AtomicStampedReference.md b/second/week_02/79/AtomicStampedReference.md new file mode 100644 index 0000000..292391a --- /dev/null +++ b/second/week_02/79/AtomicStampedReference.md @@ -0,0 +1,205 @@ +### [Java 8] AtomicStampedReference + +CAS解决了读取-修改-更新操作的原子性问题,但在使用中存在一个ABA问题 + +ABA问题的源头在于,CAS操作需要提供一个期望值(被比较值),很多时候这个期望值是由CAS操作之前读取,就在一个线程读取(比如读到了值A)和CAS操作之间,可能会有其他线程将CAS操作的内存地址的值改成其他值(比如B)然后又改回来(A),等到该线程再执行CAS时发现内存地址的值(A)和期望值(A)还是一样的,CAS操作成功,但其实这期间该内存地址的值是被修改过的,CAS并没有感知到,这就是ABA问题 + +根据场景,很多时候ABA问题不会引起什么后果,比如对于数值基本类型的原子读取-修改-更新操作,绝大部分情况只关心值是多少,至于是不是变成别的又变回来无所谓,除非特别关心变化窗口 + +但在某些无锁数据结构实现中,ABA问题就会产生严重后果,比如无锁链栈的实现中,在出栈时会先读取 `top.next` 作为期望值,然后再CAS操作通过比较当前 `top.next` 和期望值决定是否将 `top.next` 的值设置为期望值的 `next`,相当于将栈顶占位节点直接指向第二个有效节点并返回第一个有效节点,假设起始时无锁链栈节点是:top --> A --> C,就在线程1读取完 `top.next` 和CAS操作(判断是否是A如果是改成C)的间隙中,线程2先出栈A再入栈B然后又入栈A,此时无锁链栈节点是:top --> A --> B --> C,这是线程1再执行CAS时发现 `top.next` 还是A,则成功将 `top.next` 设置为C,B节点丢失 + +ABA问题的解决方案就是给值再附带一个标识,只要更新过,即使发生ABA值还是一样的,但标识不一样就能感知到。根据感知的程度不同,juc引入了两个类以解决ABA问题: +* `AtomicMarkableReference` :标识是一个 `boolean`,只感知是否更新过 +* `AtomicStampedReference`:标识是一个 `int`,可以感知更新更详细的信息,比如将标识设置为版本号,可以感知发生了多少次更新 + + +``` java +public class AtomicStampedReference { + + // 由于CAS操作只针对一个变量地址,因此需要将reference引用和stamp标识封在一个类型中,作为一个容器进行CAS操作 + private static class Pair { + final T reference; + final int stamp; + private Pair(T reference, int stamp) { + this.reference = reference; + this.stamp = stamp; + } + // Pair的构造必须给定reference引用和stamp标识 + static Pair of(T reference, int stamp) { + return new Pair(reference, stamp); + } + } + + // 保存封装当前reference引用和相应stamp标识的容器 + // volatile 关键字保证读取pair时从主内存刷新到工作内存,写入时从工作内存同步到主内存,保证多线程可见性 + private volatile Pair pair; + + /** + * Creates a new {@code AtomicStampedReference} with the given + * initial values. + * + * @param initialRef the initial reference + * @param initialStamp the initial stamp + */ + public AtomicStampedReference(V initialRef, int initialStamp) { + // 给定初始reference引用和相应的初始stamp标识初始化 + pair = Pair.of(initialRef, initialStamp); + } + + /** + * Returns the current value of the reference. + * + * @return the current value of the reference + */ + public V getReference() { + // 读取当前pair容器,然后返回reference引用 + return pair.reference; + } + + /** + * Returns the current value of the stamp. + * + * @return the current value of the stamp + */ + public int getStamp() { + // 读取当前pair容器,然后返回stamp标识 + return pair.stamp; + } + + /** + * Returns the current values of both the reference and the stamp. + * Typical usage is {@code int[1] holder; ref = v.get(holder); }. + * + * @param stampHolder an array of size of at least one. On return, + * {@code stampholder[0]} will hold the value of the stamp. + * @return the current value of the reference + */ + public V get(int[] stampHolder) { + // 先读取当前pair容器 + Pair pair = this.pair; + // 传入的stampHolder是用来存储当前stamp标识的,由于Java没有引用传递因此传递数组,这样调用方可以访问自己传入的数组获取返回的reference引用对应的stamp标识 + stampHolder[0] = pair.stamp; + // 返回reference引用 + return pair.reference; + } + + /** + * Atomically sets the value of both the reference and stamp + * to the given update values if the + * current reference is {@code ==} to the expected reference + * and the current stamp is equal to the expected stamp. + * + *

May fail + * spuriously and does not provide ordering guarantees, so is + * only rarely an appropriate alternative to {@code compareAndSet}. + * + * @param expectedReference the expected value of the reference + * @param newReference the new value for the reference + * @param expectedStamp the expected value of the stamp + * @param newStamp the new value for the stamp + * @return {@code true} if successful + */ + // 在OpenJDK 8中和compareAndSet一样 + public boolean weakCompareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + return compareAndSet(expectedReference, newReference, + expectedStamp, newStamp); + } + + /** + * Atomically sets the value of both the reference and stamp + * to the given update values if the + * current reference is {@code ==} to the expected reference + * and the current stamp is equal to the expected stamp. + * + * @param expectedReference the expected value of the reference + * @param newReference the new value for the reference + * @param expectedStamp the expected value of the stamp + * @param newStamp the new value for the stamp + * @return {@code true} if successful + */ + // 核心方法,原子同时更新reference引用和stamp标识 + 当前pair容器中的reference引用以及stamp标识分别和期望的expectedReference及expectedStamp相同时 + public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + // 先读取当前pair容器 + Pair current = pair; + return + // 如果当前pair容器的reference引用及stamp标识不分别和期望的expectedReference及expectedStamp相等则更新失败 + expectedReference == current.reference && + expectedStamp == current.stamp && + // 如果当前pair容器的reference引用及stamp标识分别和欲更新的newReference及newStamp相等则无需再CAS操作了,直接返回更新成功 + ((newReference == current.reference && + newStamp == current.stamp) || + // 用欲更新的newReference及newStamp构造新pair容器,CAS操作判断当前pair容器是否和之前读取的相同,若相同就更新为新的pair容器并返回更新成功,否则返回更新失败 + casPair(current, Pair.of(newReference, newStamp))); + } + + /** + * Unconditionally sets the value of both the reference and stamp. + * + * @param newReference the new value for the reference + * @param newStamp the new value for the stamp + */ + // 无条件更新reference引用及相应的stamp + public void set(V newReference, int newStamp) { + // 先读取当前pair容器 + Pair current = pair; + // 如果当前容器的reference引用及stamp标识分别和欲更新的newReference及newStamp相同则无需再构造新pair,直接返回 + if (newReference != current.reference || newStamp != current.stamp) + this.pair = Pair.of(newReference, newStamp); + } + + /** + * Atomically sets the value of the stamp to the given update value + * if the current reference is {@code ==} to the expected + * reference. Any given invocation of this operation may fail + * (return {@code false}) spuriously, but repeated invocation + * when the current value holds the expected value and no other + * thread is also attempting to set the value will eventually + * succeed. + * + * @param expectedReference the expected value of the reference + * @param newStamp the new value for the stamp + * @return {@code true} if successful + */ + // 如果当前pair容器的reference引用和expectedReference相同尝试更新相应的stamp标识 + public boolean attemptStamp(V expectedReference, int newStamp) { + Pair current = pair; + return + expectedReference == current.reference && + (newStamp == current.stamp || + casPair(current, Pair.of(expectedReference, newStamp))); + } + + // Unsafe mechanics + + // 因为在rt.jar包中,由启动类加载器加载,因此可以直接由getUnsafe()获取到Unsafe实例 + private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); + // 缓存实例字段pair在AtomicStampedReference实例中的地址偏移,用于Unsafe的CAS操作 + private static final long pairOffset = + objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); + + // CAS比较-更新pair容器 + private boolean casPair(Pair cmp, Pair val) { + return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); + } + + // 获取实例字段pair在AtomicStampedReference实例中的地址偏移 + static long objectFieldOffset(sun.misc.Unsafe UNSAFE, + String field, Class klazz) { + try { + return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); + } catch (NoSuchFieldException e) { + // Convert Exception to corresponding Error + NoSuchFieldError error = new NoSuchFieldError(field); + error.initCause(e); + throw error; + } + } +} +``` diff --git a/second/week_02/79/Unsafe.md b/second/week_02/79/Unsafe.md new file mode 100644 index 0000000..04e5d80 --- /dev/null +++ b/second/week_02/79/Unsafe.md @@ -0,0 +1,509 @@ +### [Java 8] Unsafe + +提供低级别、不安全操作的 native 方法,例如手动管理内存等。基本上是通过JNI访问本地C++库 + +#### 构造相关 +``` java +// 对应C++库中的Java_sun_misc_Unsafe_registerNatives函数,这是JNI名称映射规则。在这个函数内注册了所有Unsafe内方法对应的原生函数,通过这个注册,这些原生函数就不需要按JNI名称映射规则加那一坨前缀 +private static native void registerNatives(); + +static { + // 注册C++原生函数 + registerNatives(); + // 注册到反射过滤方法表中,反射时找不到getUnsafe方法 + sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); +} + +// 单例 +private Unsafe() {} + +private static final Unsafe theUnsafe = new Unsafe(); + +@CallerSensitive +public static Unsafe getUnsafe() { + // 配合@CallerSensitive注解找到最源头的调用者所在类 + Class caller = Reflection.getCallerClass(); + // 判断调用者所在类是否由启动类加载器加载 + if (!VM.isSystemDomainLoader(caller.getClassLoader())) + // 如果不是启动类加载器加载,则抛出异常,以此限制Unsafe的使用范围 + // 但可以通过反射theUnsafe绕过 + throw new SecurityException("Unsafe"); + return theUnsafe; +} +``` + + +#### 内存操作 +``` java +// 由变量o地址偏移offset取int值;如果o为null,则offset为绝对内存地址 +public native int getInt(Object o, long offset); + +// 由变量o地址偏移offset设置int值;如果o为null,则offset为绝对内存地址 +public native void putInt(Object o, long offset, int x); + +// 以下同上两个方法 +public native Object getObject(Object o, long offset); + +public native void putObject(Object o, long offset, Object x); + +public native boolean getBoolean(Object o, long offset); + +public native void putBoolean(Object o, long offset, boolean x); + +public native byte getByte(Object o, long offset); + +public native void putByte(Object o, long offset, byte x); + +public native short getShort(Object o, long offset); + +public native void putShort(Object o, long offset, short x); + +public native char getChar(Object o, long offset); + +public native void putChar(Object o, long offset, char x); + +public native long getLong(Object o, long offset); + +public native void putLong(Object o, long offset, long x); + +public native float getFloat(Object o, long offset); + +public native void putFloat(Object o, long offset, float x); + +public native double getDouble(Object o, long offset); + +public native void putDouble(Object o, long offset, double x); + +// 由地址address取byte值 +public native byte getByte(long address); + +// 由地址address设置byte值 +public native void putByte(long address, byte x); + +// 以下同上两个方法 +public native short getShort(long address); + +public native void putShort(long address, short x); + +public native char getChar(long address); + +public native void putChar(long address, char x); + +public native int getInt(long address); + +public native void putInt(long address, int x); + +public native long getLong(long address); + +public native void putLong(long address, long x); + +public native float getFloat(long address); + +public native void putFloat(long address, float x); + +public native double getDouble(long address); + +public native void putDouble(long address, double x); + +// 以下两个方法同 getLong(long address) 和 putLong(long address, long x) 的区别在于指针的大小不一定是8个字节,具体大小由C++编译器决定。针对指针的方法只读/写指针大小的字节,而针对long的方法会读/写整8个字节 +// 由地址address获取指针 +public native long getAddress(long address); + +// 由地址address设置指针 +public native void putAddress(long address, long x); + +// 分配一块bytes字节大小的内存,这块内存属于堆外内存,不由JVM直接管控 +public native long allocateMemory(long bytes); + +// 扩充起始地址为address内存块到bytes字节大小 +public native long reallocateMemory(long address, long bytes); + +// 将由变量o地址偏移offset的地址起始大小为bytes字节的内存块每字节都设置为value;如果o为null,则offset为起始地址 +public native void setMemory(Object o, long offset, long bytes, byte value); + +// 将address地址起始大小为bytes字节的内存块每字节都设置为value +public void setMemory(long address, long bytes, byte value) { + setMemory(null, address, bytes, value); +} + +// 将由变量srcBase地址偏移srcOffset的地址起始大小为bytes的内存块拷贝到由变量destBase地址偏移destOffset的起始地址;如果srcBase或destBase为null,则相应srcOffset或destOffset为起始地址 +public native void copyMemory(Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes); + +// 将srcAddress地址起始大小为bytes的内存块拷贝到destAddress起始地址 +public void copyMemory(long srcAddress, long destAddress, long bytes) { + copyMemory(null, srcAddress, null, destAddress, bytes); +} + +// 释放内存块 +public native void freeMemory(long address); +``` + + +#### 类型及对象操作 +``` java +// 获取给定静态字段相对类型地址的偏移量 +public native long staticFieldOffset(Field f); + +// 获取静态字段所属的Class对象 +public native Object staticFieldBase(Field f); + +// 获取给定对象实例字段相对对象地址的偏移量 +public native long objectFieldOffset(Field f); + +// 获取数组中第一个元素相对数组地址的偏移量 +public native int arrayBaseOffset(Class arrayClass); + +// 贴心地获取了元素分别为8个基本类型和Object的数组的首元素地址偏移量并缓存好 +public static final int ARRAY_BOOLEAN_BASE_OFFSET + = theUnsafe.arrayBaseOffset(boolean[].class); + +public static final int ARRAY_BYTE_BASE_OFFSET + = theUnsafe.arrayBaseOffset(byte[].class); + +public static final int ARRAY_SHORT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(short[].class); + +public static final int ARRAY_CHAR_BASE_OFFSET + = theUnsafe.arrayBaseOffset(char[].class); + +public static final int ARRAY_INT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(int[].class); + +public static final int ARRAY_LONG_BASE_OFFSET + = theUnsafe.arrayBaseOffset(long[].class); + +public static final int ARRAY_FLOAT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(float[].class); + +public static final int ARRAY_DOUBLE_BASE_OFFSET + = theUnsafe.arrayBaseOffset(double[].class); + +public static final int ARRAY_OBJECT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(Object[].class); + +// 获取数组中一个元素的字节大小 +public native int arrayIndexScale(Class arrayClass); + +// 贴心地获取了元素分别为8个基本类型和Object的数组的元素字节大小并缓存好 +public static final int ARRAY_BOOLEAN_INDEX_SCALE + = theUnsafe.arrayIndexScale(boolean[].class); + +public static final int ARRAY_BYTE_INDEX_SCALE + = theUnsafe.arrayIndexScale(byte[].class); + +public static final int ARRAY_SHORT_INDEX_SCALE + = theUnsafe.arrayIndexScale(short[].class); + +public static final int ARRAY_CHAR_INDEX_SCALE + = theUnsafe.arrayIndexScale(char[].class); + +public static final int ARRAY_INT_INDEX_SCALE + = theUnsafe.arrayIndexScale(int[].class); + +public static final int ARRAY_LONG_INDEX_SCALE + = theUnsafe.arrayIndexScale(long[].class); + +public static final int ARRAY_FLOAT_INDEX_SCALE + = theUnsafe.arrayIndexScale(float[].class); + +public static final int ARRAY_DOUBLE_INDEX_SCALE + = theUnsafe.arrayIndexScale(double[].class); + +public static final int ARRAY_OBJECT_INDEX_SCALE + = theUnsafe.arrayIndexScale(Object[].class); + +// 判断该类型是否需要初始化 +public native boolean shouldBeInitialized(Class c); + +// 确保类型已初始化 +public native void ensureClassInitialized(Class c); + +// 运行时创建类,可绕过JVM安全检查。其默认类加载器及保护域继承自调用者 +public native Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, + ProtectionDomain protectionDomain); + +// 运行时创建匿名类,不关联任何类加载器或保护域,只要没有实例或者该类的Class对象的引用就可以被GC +public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches); + + +// 创建实例,绕过构造函数调用等对象初始化阶段,即使没有公有构造函数依然可以用此方法创建实例 +public native Object allocateInstance(Class cls) + throws InstantiationException; +``` + + +#### 系统相关信息 +``` java +// 获取指针大小;32位系统返回4,64位系统返回8 +public native int addressSize(); + +// 贴心地获取了指针大小并缓存好 +public static final int ADDRESS_SIZE = theUnsafe.addressSize(); + +// 获取内存页大小 +public native int pageSize(); + +// 获取系统平均负载信息(uptime命令查看的系统过去1、5、15分钟三个样本的平均负载),可以选择获取几个样本 +public native int getLoadAverage(double[] loadavg, int nelems); +``` + + +#### 异常 +``` java +// 抛异常,可以像使用非受检异常一样抛受检异常 +public native void throwException(Throwable ee); +``` + + +#### 内存屏障 +内存屏障旨在解决2个问题: +1. 多核CPU缓存引发的数据可见性问题 +(1) 写数据时CPU可能只写入Store Buffer,还未同步到内存并通知其他CPU +(2) 内存更新的通知缓冲在Invalid Queue中还未来得及处理,CPU本地Cache中的数据其实已经失效 +这两点使得在一个CPU更新数据其他CPU并未及时感知到,即多线程共享变量可见性问题 + +2. CPU及编译器指令重排引发的有序性问题 +(1) 编译器和CPU都会进行指令的重排,一个线程下的指令乱序可能导致另一个线程结果不确定 +(2) Store Buffer也可能会引起写指令乱序,冲刷Store Buffer更新到内存的顺序不保证和写入Store Buffer的顺序相同,这会导致即使CPU写指令未乱序但其他CPU看到的数据更新可能是乱序的 + +CPU方面解决这两个问题的方法就是内存屏障,通常CPU提供了三种内存屏障指令: +1. LFENCE:即Load Barrier +(1) LFENCE 指令后的读指令不能重排到 LFENCE 指令之前,LFENCE 指令不能重排到 LFENCE 指令前的读指令之前。其实就是 LFENCE 指令两边的读指令不能跨越 LFENCE 指令这道屏障 +(2) LFENCE 指令清空Invalid Queue,使缓存在Invalid Queue中的失效信号得到响应,即失效对应的CPU缓存 +=> 这两点使 LFENCE 指令前的数据更新(更新到内存)对 LFENCE 指令后的读指令可见 + +2. SFENCE:即Store Barrier +(1) SFENCE 指令前的写指令不能重排到 SFENCE 指令之后,即SFENCE 指令前的写指令不能跨越 SFENCE 指令这道屏障 +(2) SFENCE 指令冲刷Store Buffer,将缓冲在Store Buffer中的更新数据更新到内存,并向其他CPU发出失效信号 +=> 这两点使 SFENCE 指令前的内存更新可以对其他CPU可见(当其他CPU响应了缓冲在Invalid Queue中的失效信号后才可见) + +3. MFENCE:即Full Barrier,集结了 LFENCE 和 SFENCE 的能力 + +对于X86架构CPU,采用强内存模型,对开发者并发编程提供了很大程度的支持,强保证了以下三种指令组合有序性: +1. 读指令不能和其他读指令重排序 +2. 写指令不能和之前的读指令重排序 +3. 写指令不能和其他写指令重排序 + +此外X86架构CPU采用FIFO的Store Buffer,保证冲刷顺序与写入顺序相同,其他CPU看到的更新不会乱序;另外X86架构CPU没有Invalid Queue缓冲,只要CPU冲刷了Store Buffer,其他CPU都可以立即响应响应缓存的失效 +因此在X86架构CPU下,CPU内存屏障指令在有序性问题上的效果实际为: +1. LFENCE:无效果,因为强内存模型已经保证了读指令不和其他读指令重排 +2. SFENCE:无效果,因为强内存模型已经保证了写指令不和其他写指令重排 +3. MFENCE:保证读指令不能和之前的写指令重排 + +在可见性问题上的效果实际为: +1. LFENCE:无效果,因为架构上没有Invalid Queue缓冲 +2. SFENCE:冲刷Store Buffer +3. MFENCE:冲刷Store Buffer + +因此在X86架构CPU下,LFENCE 和 SFENCE 几乎无开销或开销极小,而 MFENCE 相对来说开销昂贵,因为毕竟要阻挡读指令重排到写指令前 + + +在有序性方面,因为顺序总是对于两条读/写指令而言的,因此根据排列组合Java内存模型抽象出4种内存屏障语义: +1. LoadLoad +Load1; LoadLoad; Load2 +确保 Load1 先于 Load2 及其之后的所有读指令 +X86架构CPU强内存模型已保证,无需任何指令 +2. StoreStore +Store1; StoreStore; Store2 +确保 Store1 先于 Store2 及其之后所有写指令 +为了避免Store Buffer引发其他CPU看到数据更新乱序,通常在该屏障处让CPU冲刷Store Buffer让 Store1 的更新可以先对其他CPU可见 +X86架构CPU由于Store Buffer是FIFO,因此在该屏障处无需冲刷Store Buffer,再加上强内存模型保证写指令不重排,故无需任何指令 +3. LoadStore +Load1; LoadStore; Store2 +确保 Load1 先于 Store2 及其之后的所有写指令 +X86架构CPU强内存模型已保证,无需任何指令 + +4. StoreLoad +Store1; StoreLoad; Load2 +确保 Store1 (且对其他CPU可见)先于 Load2 及其之后所有读指令 +X86架构CPU需要使用 MFENCE 指令保证 + +因此在X86架构CPU下LoadLoad、StoreStore以及LoadStore语义的内存屏障实现上就是空操作;由于StoreLoad使用了大杀器 MFENCE,因此在效果上包含了所有其他三种语义的屏障(对于几乎所有架构的CPU,StoreLoad所采用的指令在效果上覆盖了其他三种语义的屏障) + +另外在Java内存模型中线程工作内存概念就是CPU缓存的抽象 + +同时编译器也遵守这些屏障的语义,在相应的屏障处按相应规则避免编译优化引起的乱序 + + +``` java +// 实现LoadLoad + LoadStore语义的屏障,即确保屏障前的读操作先于屏障后的读/写操作 +// 对于X86架构CPU就是空操作 +public native void loadFence(); + +// 实现StoreStore + LoadStore语义的屏障,即确保屏障前的读/写操作先于屏障后的写操作 +// 对于X86架构CPU就是空操作 +public native void storeFence(); + +// 实现StoreLoad语义的全能屏障,即确保屏障两边的读/写操作都不能跨越该屏障,且使屏障前的写操作结果对其他线程可见 +// 对于X86架构CPU就是相对昂贵的MFENCE指令(对任何架构的CPU实现都是相对其他屏障非常昂贵的) +public native void fullFence(); +``` + +volatile语义: +1. volatile读语义: +(1) 可见性:能读到该变量在其他线程更新的结果 +(2) 有序性:volatile读后的读/写操作不能重排到volatile读之前 +``` +[LFENCE] +volatile read +[LoadLoad] +[LoadStore] +``` +2. volatile写语义: +(1) 可见性:该变量的更新能够被其他线程看到 +(2) 有序性:volatile写前的读/写操作不能重排到volatile写之后 +``` +[StoreStore] +[LoadStore] +volatile write +[StoreLoad] // 不仅保证冲刷Store Buffer,同时保证和下面的volatile读操作不会乱序 +``` +`volatile` 关键字使变量在读取和赋值时分别具有volatile读和volatile写语义 + +volatile并不能解决读取-修改-更新操作的原子性问题 + +``` java +// 由变量o地址偏移offset按volatile读语义取对象 +public native Object getObjectVolatile(Object o, long offset); + +// 由变量o地址偏移offset按volatile写语义赋对象 +public native void putObjectVolatile(Object o, long offset, Object x); + +// 以下同上两个方法 +public native int getIntVolatile(Object o, long offset); + +public native void putIntVolatile(Object o, long offset, int x); + +public native boolean getBooleanVolatile(Object o, long offset); + +public native void putBooleanVolatile(Object o, long offset, boolean x); + +public native byte getByteVolatile(Object o, long offset); + +public native void putByteVolatile(Object o, long offset, byte x); + +public native short getShortVolatile(Object o, long offset); + +public native void putShortVolatile(Object o, long offset, short x); + +public native char getCharVolatile(Object o, long offset); + +public native void putCharVolatile(Object o, long offset, char x); + +public native long getLongVolatile(Object o, long offset); + +public native void putLongVolatile(Object o, long offset, long x); + +public native float getFloatVolatile(Object o, long offset); + +public native void putFloatVolatile(Object o, long offset, float x); + +public native double getDoubleVolatile(Object o, long offset); + +public native void putDoubleVolatile(Object o, long offset, double x); +``` + +lazy写(`putOrderedXXX`)类似volatile写,但只保证一部分有序性且不保证可见性,即lazy写前的读/写操作不能重排到lazy写之后,但不保证之后的操作不能重排到lazy前之前,此外不保证数据更新能够对其他线程可见 +``` +[StoreStore] +[LoadStore] +putOrderedXXX +// 这里相比volatile写没有StoreLoad屏障 +``` + +``` java +// 由变量o地址偏移offset按lazy写语义赋对象 +public native void putOrderedObject(Object o, long offset, Object x); + +// 以下同上个方法 +public native void putOrderedInt(Object o, long offset, int x); + +public native void putOrderedLong(Object o, long offset, long x); +``` + + +#### CAS操作 +CAS操作是通过CPU的原子性指令的支持,以非阻塞的方式保证了比较-更新操作的原子性。CAS相关方法解决了解决读取-修改-更新操作的原子性问题,主要用于构造无锁数据结构 + +在X86架构CPU下,CAS操作前后会有StoreLoad语义屏障 +``` java +// 由对象o地址偏移offset的变量值如果是expected,则用x替换并返回true(替换成功);否则(其他线程更新了变量值导致)不替换并返回false(替换失败) +public final native boolean compareAndSwapObject(Object o, long offset, + Object expected, + Object x); + +// 以下同上个方法 +public final native boolean compareAndSwapInt(Object o, long offset, + int expected, + int x); + +public final native boolean compareAndSwapLong(Object o, long offset, + long expected, + long x); + +// 将对象o地址偏移offset的int变量值原子性地增加delta,并返回被替换的int值 +public final int getAndAddInt(Object o, long offset, int delta) { + int v; + do { + // volatile读语义取当前int变量值 + v = getIntVolatile(o, offset); + // 尝试用CAS操作将该int变量替换为增加delta后的值,如果失败,则说明其他线程已修改该变量,当前获取的v已失效,因此循环再次重新获取变量值并尝试替换 + } while (!compareAndSwapInt(o, offset, v, v + delta)); + // 返回最后替换成功操作替换掉的变量值 + return v; +} + +// 以下同上个方法 +public final long getAndAddLong(Object o, long offset, long delta) { + long v; + do { + v = getLongVolatile(o, offset); + } while (!compareAndSwapLong(o, offset, v, v + delta)); + return v; +} + +// 将对象o地址偏移offset的int变量值原子性地替换为newValue,该方法可以返回被替换的int值(原子读-写) +public final int getAndSetInt(Object o, long offset, int newValue) { + int v; + do { + // volatile读语义取当前int变量值 + v = getIntVolatile(o, offset); + // 尝试用CAS操作将该int变量替换为newValue,如果失败,则说明其他线程已修改该变量,当前获取的v已失效,因此循环再次重新获取变量值并尝试替换 + } while (!compareAndSwapInt(o, offset, v, newValue)); + // 返回最后替换成功操作替换掉的变量值 + return v; +} + +// 以下同上个方法 +public final long getAndSetLong(Object o, long offset, long newValue) { + long v; + do { + v = getLongVolatile(o, offset); + } while (!compareAndSwapLong(o, offset, v, newValue)); + return v; +} + +public final Object getAndSetObject(Object o, long offset, Object newValue) { + Object v; + do { + v = getObjectVolatile(o, offset); + } while (!compareAndSwapObject(o, offset, v, newValue)); + return v; +} +``` + + +#### 线程调度 +``` java +// 唤醒因为调用park被阻塞的线程 +// 如果线程并未被阻塞,则下一次触发该线程park不会阻塞 +public native void unpark(Object thread); + +// 阻塞当前线程,直到其他线程触发该线程unpark,或者其他线程调用该线程的interrupt方法,或者超过了给定的超时时间 +// 如果time为0则表示不给定超时时间 +// 如果time大于0,如果isAbsolute为true则time为绝对时间,否则为相对时间 +public native void park(boolean isAbsolute, long time); +``` -- Gitee