diff --git a/week_02/59/AtomicInteger.md b/week_02/59/AtomicInteger.md new file mode 100644 index 0000000000000000000000000000000000000000..36f465a1a731dc17140dcef4fbf6e51e2ed8cadc --- /dev/null +++ b/week_02/59/AtomicInteger.md @@ -0,0 +1,190 @@ +### AtomicInteger基础介绍 + +cpu是时分复用的,也就是把cpu的时间片,分配给不同的线程/进程轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyAll()唤醒回来。在某个资源不可用的时候,就将cpu让出,把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。 + +  但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。 + +   所以就有了乐观锁的概念,他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。比如我们要说的AtomicInteger底层同步CAS就是一种乐观锁思想的应用。 + +  CAS就是Compare and Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 + +   在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。 + +​ 以上引用博客地址:https://www.cnblogs.com/lcngu/p/5405890.html + +​ 简单的来说,AtomicInteger是一种乐观锁的应用,它通过原子级别的操作,去争取资源,直到操作完成。 + +### 源码分析 + +1.AtomicInteger继承了Number类操作数字,并且可以序列化 + +```java +public class AtomicInteger extends Number implements java.io.Serializable +``` + +2.AtomicInteger定义了一个Unsafe属性,主要是用Unsafe属性的CAS算法去更新值,还有一个valueOffset值,是为了存储AtomicInteger的value值内存偏移量的。 + +```java + // setup to use Unsafe.compareAndSwapInt for updates + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + //初始化valueOffset + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + //volatile的含义是copy出来的副本时钟和内存中的值保存一致,也就是永远是最新的值 + private volatile int value; +``` + +3.有参构造函数 + +```java +//初始化一个值 +public AtomicInteger(int initialValue) { + value = initialValue; + } +``` + +4.无参构造函数 + +```java +public AtomicInteger() { +} +``` + +5.一些获取值和设置值的方法 + +```java +public final int get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new 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) { + 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); + } +``` + +6.CAS算法设置值 + +```java +/** + * 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) { + 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 + */ + public final boolean weakCompareAndSet(int expect, int update) { + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } +``` + +7.获取并增加1 + +```java +/** + * Atomically increments by one the current value. + * + * @return the previous value + */ +//这段代码的意思就是这个AtomicInteger对象的valueOffset内存偏移对应的值+1。那么对于unsafe.getAndAddInt做了些什么。这里要讲到CAS的概念了(compareAndSwap)。 + public final int getAndIncrement() { + return unsafe.getAndAddInt(this, valueOffset, 1); + } + public final int getAndAddInt(Object var1, long var2, int var4) { + int var5; + do { + //var5是当前对象var1(AtomicInteger)在内存var2的值,也是就是value + var5 = this.getIntVolatile(var1, var2); + //如果该offset内存偏移对应的值是var5,即没有被更改过,那么就更新成var5+var4。这个就是明显的乐观锁的概念。 + } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); + + return var5; + } + /** + * Atomically decrements by one the current value. + * + * @return the previous value + */ + public final int getAndDecrement() { + return unsafe.getAndAddInt(this, valueOffset, -1); + } +``` + +8.获取并增加值 + +```java +/** + * 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 + */ + public final int getAndUpdate(IntUnaryOperator updateFunction) { + int prev, next; + do { + //获取当前值 + prev = get(); + //获取更新值 + next = updateFunction.applyAsInt(prev); + //循环判断,直到当前内存值和pre中的值相等则替换 + } while (!compareAndSet(prev, next)); + return prev; + } +``` + +### 总结 + +AtomicInteger可以使得我们操作数据更加安全便捷,并且这个思路也可以让我们应用到具体的项目中。 + +​ \ No newline at end of file diff --git a/week_02/59/AtomicStampedReference.md b/week_02/59/AtomicStampedReference.md new file mode 100644 index 0000000000000000000000000000000000000000..45300c709e55c680c728500ae2d11df5c83dd185 --- /dev/null +++ b/week_02/59/AtomicStampedReference.md @@ -0,0 +1,165 @@ +### AtomicStampedReference介绍 + +​ 之前是AtomicInteger,但是该类在CAS操作的时候会遇到ABA的问题,而为了解决这个问题,AtomicStampedReference提供了版本号的概念,即:同一值也有不通版本的概念,从而更好地解决ABA问题的发生。 + +### 源码介绍 + +1.该类下定义了一个内部类,源码如下 + +```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); + } + } +``` + +2.一个用volatile修饰的pair属性变量 + +```java +private volatile Pair pair; +``` + +3.AtomicStampedReference构造方法 + +```java +/** + * 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) { + pair = Pair.of(initialRef, initialStamp); + } +``` + +4.获取值和版本 + +```java +/** + * Returns the current value of the reference. + * + * @return the current value of the reference + */ + public V getReference() { + return pair.reference; + } + + /** + * Returns the current value of the stamp. + * + * @return the current value of the stamp + */ + public int getStamp() { + return pair.stamp; + } +``` + +5.设置版本并获取值 + +```java + /** + * 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 = this.pair; + stampHolder[0] = pair.stamp; + return pair.reference; + } +``` + +6. + +```java + public boolean weakCompareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + //CAS算法,通过期望的值和版本,去设置当前的值和版本 + return compareAndSet(expectedReference, newReference, + expectedStamp, newStamp); + } + public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + //获取当前内存可见的pair + Pair current = pair; + return + //现在值和期望现在值里面2个中,有一个不一样直接返回false不需要更新。 + expectedReference == current.reference && + expectedStamp == current.stamp && + //现在值和期望现在值里面2个一样需要更新 + //新值和现在值2个都一样返回true不需要更新 + ((newReference == current.reference && + newStamp == current.stamp) || + //现在值和期望现在值里面2个都一样,并且新值和现在值有一个不一样,需要更新。 + casPair(current, Pair.of(newReference, newStamp)));//改变旧的pair为新的pair,新的pair要重新构造一个新的。 + } +``` + +7.设置值 + +```java +public void set(V newReference, int newStamp) { + Pair current = pair; + //其中一个不相等,更换 + if (newReference != current.reference || newStamp != current.stamp) + this.pair = Pair.of(newReference, newStamp); + } +``` + +8. + +```java +public boolean attemptStamp(V expectedReference, int newStamp) { + Pair current = pair; + return + //如果期望值和当前值不相等直接返回false + expectedReference == current.reference && + (newStamp == current.stamp || + //如果值相等,版本也相等,就返回true + //如果值相等,版本不相等,则更新一个pair + casPair(current, Pair.of(expectedReference, newStamp))); + } + + // Unsafe mechanics + + private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); + private static final long pairOffset = + objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);//获取pair的内存偏移量 + + private boolean casPair(Pair cmp, Pair val) { + return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);//改变里面的pair从cmp到val + } + //获取一个field的内存偏移量 + 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; + } + } +``` + +### 总结 + +通过AtomicStampedReference问题我们可以解决ABA问题 \ No newline at end of file diff --git a/week_02/59/LongAdder.md b/week_02/59/LongAdder.md new file mode 100644 index 0000000000000000000000000000000000000000..222674f9258b66ffab0cb3200815fafc35581fa4 --- /dev/null +++ b/week_02/59/LongAdder.md @@ -0,0 +1,268 @@ +### LongAdder介绍 + +​ **AtomicLong**中有个内部变量**value**保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。 + +​ **LongAdder**的基本思路就是**分散热点**,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。 + +​ 总之,低并发、一般的业务场景下AtomicLong是足够了。如果并发量很多,存在大量写多读少的情况,那LongAdder可能更合适。 + +### LongAdder原理 + +​ **AtomicLong**是多个线程针对单个热点值value进行原子操作。而**LongAdder**是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作。 + +​ 但是对于**LongAdder**来说,内部有一个`base`变量,一个`Cell[]`数组。 +` base`变量:非竞态条件下,直接累加到该变量上 +` Cell[]`数组:竞态条件下,累加个各个线程自己的槽`Cell[i]`中 +最终的结果是base变量加上cell里面每个值得总值。 + +### 源码分析 + +​ 1.继承类和实现类 + +```java +public class LongAdder extends Striped64 implements Serializable + +``` + +1.继承了Striped64 +来看下Striped64的内部结构,这个类实现一些核心操作,处理64位数据。 +Striped64只有一个空构造器,初始化时,通过Unsafe获取到类字段的偏移量,以便后续CAS操作 + +```java +// Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long BASE; + private static final long CELLSBUSY; + private static final long PROBE; + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class sk = Striped64.class; + BASE = UNSAFE.objectFieldOffset + (sk.getDeclaredField("base")); + CELLSBUSY = UNSAFE.objectFieldOffset + (sk.getDeclaredField("cellsBusy")); + Class tk = Thread.class; + PROBE = UNSAFE.objectFieldOffset + (tk.getDeclaredField("threadLocalRandomProbe")); + } catch (Exception e) { + throw new Error(e); + } + } +``` + +​ 在Striped64定义了一个内部Cell类,这就是我们之前所说的槽,每个Cell对象存有一个value值,可以通过Unsafe来CAS操作它的值: + +```java +/** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + @sun.misc.Contended static final class Cell { + volatile long value; + Cell(long x) { value = x; } + final boolean cas(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long valueOffset; + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class ak = Cell.class; + valueOffset = UNSAFE.objectFieldOffset + (ak.getDeclaredField("value")); + } catch (Exception e) { + throw new Error(e); + } + } + } +``` + +​ 其他字段: + +```java + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors();//获取cpu核数,决定槽数组大小 + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells;//槽数组,大小为2的幂 + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base;//基础数据,不竞争或竞争失败使用的 + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + * 锁标识 + * cells初始化或扩容时,通过CAS操作将此标识设置为1-加锁状态;完毕之后设置为0-无锁状态 + */ + + transient volatile int cellsBusy; +``` + +### LongAdder的核心方法 + +```java +public void add(long x) {//增加值 + Cell[] as; long b, v; int m; Cell a; + //先判断cells是否被初始化,没被初始化,则调用casBase累加值 + //被初始化之后说明已发送冲突,则往下走 + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + //再次判断是否初始化,没被初始化通过hash去获取一个cells的下标 + //如果a==null为真则调用longAccumulate(x, null, uncontended); + //如果a!null为家再去调用a.cas累加值,累加成功则返回,不成功则调用 longAccumulate(x, null, uncontended); + if (as == null || (m = as.length - 1) < 0 || + (a = as[getProbe() & m]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + longAccumulate(x, null, uncontended); + } + } +////这也是LongAdder设计的精妙之处:尽量减少热点冲突,不到最后万不得已,尽量将CAS操作延迟。 + + + +public long sum() {//求所有值的和 + Cell[] as = cells; Cell a; + long sum = base; + if (as != null) { + for (int i = 0; i < as.length; ++i) { + if ((a = as[i]) != null) + sum += a.value; + } + } + return sum; + } +public void reset() {//重置整个LongAddr + Cell[] as = cells; Cell a; + base = 0L; + if (as != null) { + for (int i = 0; i < as.length; ++i) { + if ((a = as[i]) != null) + a.value = 0L; + } + } + } +public long sumThenReset() {//求和之后重置 + Cell[] as = cells; Cell a; + long sum = base; + base = 0L; + if (as != null) { + for (int i = 0; i < as.length; ++i) { + if ((a = as[i]) != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } +``` + +longAccumulate方法 + +```java +final void longAccumulate(long x, LongBinaryOperator fn, + boolean wasUncontended) { + int h; + if ((h = getProbe()) == 0) {//获取一个hash值 + ThreadLocalRandom.current(); // force initialization + h = getProbe(); + wasUncontended = true; + } + boolean collide = false;//如果hash取模获取得到的cell党员不是null,则为true,可看作扩容的意思 // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + //case1:cells已经被初始化了 + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) {//当前线程hash值运算后映射得到cells单元为null,说明该cell没有被使用 + if (cellsBusy == 0) {//cells数组没有在扩容// Try to attach new Cell + Cell r = new Cell(x);// Optimistically create。创建 + if (cellsBusy == 0 && casCellsBusy()) {//尝试加锁,cellsBush=1 + boolean created = false; + try {// Recheck under lock有锁的情况之下再检测一遍之前的判断 + Cell[] rs; int m, j;//将cell单元加到cell数组上 + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + cellsBusy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, ((fn == null) ? v + x : + fn.applyAsLong(v, x)))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + + else if (cellsBusy == 0 && casCellsBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h = advanceProbe(h); + } + //Case2:cells没有加锁,且没有初始化,则尝试对它进行加锁,并初始化cells数组 + else if (cellsBusy == 0 && cells == as && casCellsBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + cellsBusy = 0; + } + if (init) + break; + } + //CASE3:cells正在进行初始化,则尝试直接在base上进行累加操作 + else if (casBase(v = base, ((fn == null) ? v + x : + fn.applyAsLong(v, x)))) + break; // Fall back on using base + } + } +``` + +上述代码首先给当前线程分配一个hash值,然后进入一个自旋,这个自旋分为三个分支: + +- **CASE1:Cell[]数组已经初始化** +- **CASE2:Cell[]数组未初始化** +- **CASE3:Cell[]数组正在初始化中** + +### 总结 + +LongAdder因为sum求和的原因,会导致sum得到的值不一定准确。 + diff --git a/week_02/59/Unsafe.md b/week_02/59/Unsafe.md new file mode 100644 index 0000000000000000000000000000000000000000..52807e484bdb075e3e04ca8a2bb07b5b7dc44eb8 --- /dev/null +++ b/week_02/59/Unsafe.md @@ -0,0 +1,247 @@ +### Unsafe类简介 + +​ Unsafe类通过JNI的方式访问本地的c++实现库,使得java具有了直接操作内存空间的能力。但是操作内存空间是很危险的,所以一般使用要慎重。Unsafe提供了硬件级别的原子性操作。Unsafe类就像是java给开发者提供的一个后门,让开发者使用它可以在任意内存地址读写数据,而不用经过jvm调度,也使得java更加灵活,对于系统调优有一定的帮助,优秀的开发者可以使用这个类造一些实用的轮子。 + +### 获取Unsafe类 + +​ 1.Unsafe类的构造方法是私有的,所以无法通过构造方法去获取它。 + +```Java +private Unsafe() {} +``` + +​ 2.Unsafe类提供了一个静态的get的方法,可以尝试使用这个方法去获取Unsafe. + +```java +@CallerSensitive + public static Unsafe getUnsafe() { + Class var0 = Reflection.getCallerClass(); + if (!VM.isSystemDomainLoader(var0.getClassLoader())) { + throw new SecurityException("Unsafe"); + } else { + return theUnsafe; + } + } +``` + + + +```Java +public static void main(String[] args) { + Unsafe u = Unsafe.getUnsafe(); + System.err.println(u); +} +``` + +运行之后报错 + +```java +Exception in thread "main" java.lang.SecurityException: Unsafe + at sun.misc.Unsafe.getUnsafe(Unsafe.java:90) + at hello.world.main(world.java:117) +``` + +抛出异常:throw new SecurityException("Unsafe"); + +​ 抛出异常就说明不满足if条件,isSystemDomainLoader(caller.getClassLoader())方法就是检查调用者的类加载器是否是启动类加载器,抛出异常就是因为我们自己创建的类不是由启动类加载器加载,而是默认由系统类加载器加载的,故抛出异常。 + +##### 可通过反射来获取Unsafe类 + +``` java +final Field theUnsafe; + try { + theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Unsafe unsafe = (Unsafe) theUnsafe.get(null); + System.out.println(unsafe.getClass().getClassLoader()); + System.out.println(unsafe); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } +``` + + + +#### 一些方法 + +1普通方法 + +```java +public native int getInt(Object var1, long var2); + +public native void putInt(Object var1, long var2, int var4); + +public native Object getObject(Object var1, long var2); + +public native void putObject(Object var1, long var2, Object var4); +``` + +​ 以上简单的放是通过操作内存从一个给定的java变量获取或者设置值 + +2.获取对象字段或静态属性的内存地址偏移量 + +```java + public native long staticFieldOffset(Field var1); + + public native long objectFieldOffset(Field var1); +``` + +通过这两个方法,将对象的field传进去,获取改属性在内存地址中的内存偏移量。举例: + +```java +@Test +public void testGetFieldOffset() throws NoSuchFieldException, IllegalAccessException { + final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Unsafe unsafe = (Unsafe) theUnsafe.get(null); + + //获取字段偏移量 + long address = unsafe.objectFieldOffset(Student.class.getDeclaredField("name")); + System.out.println(address); + + //获取静态字段偏移量 + long staticAddress = unsafe.staticFieldOffset(Student.class.getDeclaredField("info")); + System.out.println(staticAddress); +} + +``` + +3.内存的分配,释放和复制 + +``` java +//给值分配内存 +public native long allocateMemory(long var1); + +//重新分配内存(扩展) +public native long reallocateMemory(long var1, long var3); + +//释放该部分内存 +public native void freeMemory(long var1); + +//拷贝内存空间,具体对应关系暂时不清楚 +public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7); +//第一个参数是源地址,第二个是目标地址,第三个值是长度? +public void copyMemory(long var1, long var3, long var5) { + this.copyMemory((Object)null, var1, (Object)null, var3, var5); +} + +//给指定内存地址设置固定值 +public native void setMemory(Object var1, long var2, long var4, byte var6); + +public void setMemory(long var1, long var3, byte var5) { + this.setMemory((Object)null, var1, var3, var5); +} +``` + +4.内存屏障 + +​ 为了保证内存的可见性,java编译器在生成指令序列的适当位置会插入内存屏障指令类禁止特定类型的处理器重排序,java内存模型(JMM)把内存屏障指令分为4类: + +LoadLoad(Load1,LoadLoad,Load2):确保load1数据的装载先于load2及所有后序装载指令的装载。 +LoadStore(Load1,LoadStore,Store2):确保Load1数据的装载先于Store2及所有后序存储指令刷新内存。 +StoreStore(Store1,StoreStore,Store2):确保Store1数据刷新内存先于Store2及所有后序存储指令刷新内存。 +StoreLoad(Store1,StoreLoad,Load2):确保Store1数据刷新内存先于Load2及所有后序装载指令的装载。该屏蔽指令会使该屏蔽之前的所有内存访问指令执行完成后才执行屏蔽之后的内存访问指令。并且这个指令是一个全能的指令,同时具备以上三个内存屏蔽指令的功能。 + +```java +//确保在屏蔽之前加载(读操作),屏蔽之后的读写操作不会重排序,相当于LoadLoad 加上 LoadStore 屏障 +public void loadFence() { + theInternalUnsafe.loadFence(); +} +//确保在屏蔽之前加载和存储(读写操作),屏蔽之后的写操作不会重排序,相当于StoreStore 加上 LoadStore 屏障 +public void storeFence() { + theInternalUnsafe.storeFence(); +} +//确保在屏蔽之前读写操作,屏蔽之后的读写操作不会重排序,同时具有以上两个方法的功能,还具有StoreLoad屏蔽的功能 +public void fullFence() { + theInternalUnsafe.fullFence(); +} + +``` + +5.通过操作内存获取或设置数组元素值 + +```java +//获取数组中第一个元素在内存的偏移地址 +public int arrayBaseOffset(Class arrayClass) { + return theInternalUnsafe.arrayBaseOffset(arrayClass); +} +//获取数组增量元素在内存中的地址 +public int arrayIndexScale(Class arrayClass) { + return theInternalUnsafe.arrayIndexScale(arrayClass); +} + +``` + +6.从一个给定的内存地址获取或设置一个本地指针 + +```java +//从给定的内存地址获取一个本地指针 +public long getAddress(long address) { + return theInternalUnsafe.getAddress(address); +} +//从给定的内存地址设置一个本地指针 +public void putAddress(long address, long x) { + theInternalUnsafe.putAddress(address, x); +} + +``` + +7.CAS原子性操作 + +```java +//CAS操作对象类型 +public final boolean compareAndSwapObject(Object o //对象, long offset //内存偏移量, + Object expected, //预期值 + Object x) { //新值 + return theInternalUnsafe.compareAndSetObject(o, offset, expected, x); +} +//CAS操作int类型 +public final boolean compareAndSwapInt(Object o, long offset, + int expected, + int x) { + return theInternalUnsafe.compareAndSetInt(o, offset, expected, x); +} +//CAS操作long型 +public final boolean compareAndSwapLong(Object o, long offset, + long expected, + long x) { + return theInternalUnsafe.compareAndSetLong(o, offset, expected, x); +} + +``` + +8.线程的挂起与恢复 + +Unsafe提供了线程的挂起与恢复方法,JUC中的锁会用到Unsafe提供的线程挂起与恢复方法 + +```java +//线程挂起,直到unpark调用或者打断或者超时,线程恢复。 +//isAbsolute设置是否为绝对时间,如果为true,则超时时间的单位为毫秒 +//isAbsolute为false,time设置为0,就是一直阻塞,如果time不为0,则超时时间的单位为纳秒 +public void park(boolean isAbsolute, long time) { + theInternalUnsafe.park(isAbsolute, time); +} +//线程恢复 +public void unpark(Object thread) { + theInternalUnsafe.unpark(thread); +} +使用方法: +@Test +public void testParkAndUnPark() throws NoSuchFieldException, IllegalAccessException { + final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Unsafe unsafe = (Unsafe) theUnsafe.get(null); + + //写一个线程执行一个任务 + Thread thread = new Thread(() -> System.out.println("hello")); + thread.start(); + unsafe.unpark(Thread.currentThread()); //如果不加这句,线程或一直处于阻塞状态 + unsafe.park(false,0); +} + + +``` + +### 总结: + +Unsafe这个类可以直接操作内存空间,非常实用也非常危险,要想能够使它为我们所用,还需要花很多精力研究,并多上手操作。 \ No newline at end of file