diff --git a/week_02/08/AtomicInteger-008.md b/week_02/08/AtomicInteger-008.md new file mode 100644 index 0000000000000000000000000000000000000000..4662e07aedac4ae1e0608da1518de2e14aeeab96 --- /dev/null +++ b/week_02/08/AtomicInteger-008.md @@ -0,0 +1,121 @@ +# 读源码--AtomicInteger + +## 属性 + +unsafe属性:为了保证线程安全,AtomicIngeger的增减方法的底层都用到了Unsafe类的cas方法 + +```java +private static final Unsafe unsafe = Unsafe.getUnsafe(); +``` + +valueOffset属性:字段的偏移地址,在cas中用来得到当前字段的值 + +```java +private static final long valueOffset; +``` + +value属性:此处用volatile修饰,volatile,被称为“轻量级的synchronized”,但只能修饰变量。 + +```java +private volatile int value; +``` + +## 方法 + +AtomicInteger的实现方法模式基本一致,用一个方法说明,getAndSet方法 + +```java +public final int getAndSet(int newValue) { + return unsafe.getAndSetInt(this, valueOffset, newValue); +} +// get在前,返回值则为修改前的值;若get在后,则返回值为修改后的值 +public final int getAndSetInt(Object var1, long var2, int var4) { + int var5; + // 此处用到cas算法,保证线程安全 + do { + var5 = this.getIntVolatile(var1, var2); + } while(!this.compareAndSwapInt(var1, var2, var5, var4)); + + return var5; +} +``` + +## CAS + +### CAS概念 + +比较并交换,unsafe中有该方法,广泛应用与文字操作。CAS是乐观锁技术,当多个线程尝试更新同一个变量时,只有一个能更新成功,而其他线程都失败。失败的线程不会被挂起,而是被告知失败,重新尝试。 + +### CAS 缺点 + +1.循环时间长开销大 + +2.只能保证一个共享变量的原子操作 + +3.ABA问题。解决办法,版本控制。Java中提供了AtomicStampedReference通过控制变量值的版本来保证CAS的正确性。 + +## volatile + +### 特性 + +1.保证了不同线程对变量操作的内存可见性(可见性); + +2.禁止指令重排序(有序性) + +3.无法保证复合操作的原子性。要想保证原子性,只能借助于synchronized,Lock及原子操作了,如Atomic系列类 + +https://juejin.im/post/5a2b53b7f265da432a7b821c volatile面试强文 + +### 并发编程三大特性 + +1.原子性:Java中对基本数据类型的读取和赋值操作 + +2.可见性:Java利用volatile来提供可见性。变量被volatile修饰是,对它的修改会立即刷新到贮存;当其他线程需要读取该变量是,会从主存中共读取新值。 + +3.有序性:JMM允许编译器和处理器对指令重排序。对单线程没有影响,对多线程有影响/ + +```java +int a = 0; +bool flag = false; + +public void write() { + a = 2; //1 + flag = true; //2 +} + +public void multiply() { + if (flag) { //3 + int ret = a * a;//4 + } + +} +// 若线程1先执行write方法中flag=true;线程2执行multiply,线程1再执行a=2; +``` + +### 应用场景 + +1.状态量标记:标记为volatile可以保证修改对线程立即可见。比synchronized,Lock有一定的效率提升。 + +2.单例模式的实现 + +```java +class Singleton{ + // 可避免初始化操作的指令重排序 + private volatile static Singleton instance = null; + + private Singleton() { + + } + + public static Singleton getInstance() { + if(instance==null) { + synchronized (Singleton.class) { + if(instance==null) + instance = new Singleton(); + } + } + return instance; + } +} +``` + diff --git a/week_02/08/AtomicInteger-008.xmind b/week_02/08/AtomicInteger-008.xmind new file mode 100644 index 0000000000000000000000000000000000000000..31e4a9908227aa77bce5090216e7ac6c9aa120e4 Binary files /dev/null and b/week_02/08/AtomicInteger-008.xmind differ diff --git a/week_02/08/AtomicStampedReference-008.md b/week_02/08/AtomicStampedReference-008.md new file mode 100644 index 0000000000000000000000000000000000000000..3614acbf332d09cb154fd0dd47baff96fffa8ed9 --- /dev/null +++ b/week_02/08/AtomicStampedReference-008.md @@ -0,0 +1,263 @@ +# 读源码--AtomicStampedReference + +## ABA问题 + +在CAS过程中,会出现ABA问题 + +```java +public class ABATest { + public static void main(String[] args) { + AtomicInteger atomicInteger = new AtomicInteger(1); + new Thread(() -> { + int value = atomicInteger.get(); + System.out.println("Thread 1 read value:"+value); + + LockSupport.parkNanos(100000000L); + if (atomicInteger.compareAndSet(value, 3)) { + System.out.println("Thread 1 update from " + value + " to 3"); + } else { + System.out.println("Thread 1 update fail !"); + } + }).start(); + + new Thread(() -> { + int value = atomicInteger.get(); + System.out.println("Thread 2 read value:" + value); + + if (atomicInteger.compareAndSet(value, 2)) { + System.out.println("Thread 2 update from "+value+" to 2"); + } + + value = atomicInteger.get(); + System.out.println("Thread 2 read value:"+value); + if (atomicInteger.compareAndSet(value, 1)) { + System.out.println("Thread 2 update from "+value+" to 3"); + } + }).start(); + } +} +``` + +结果 + +```java +Thread 1 read value:1 +Thread 2 read value:1 +Thread 2 update from 1 to 2 +Thread 2 read value:2 +Thread 2 update from 2 to 3 +Thread 1 update from 1 to 3 +``` + + + +## ABA危害 + +例子,一个无锁的栈结构 + +```java +public class ABATest1 { + static class Stack{ + private AtomicReference top = new AtomicReference(); + + static class Node { + int value; + Node next; + public Node(int value) { + this.value = value; + } + } + + // 出栈 + public Node pop() { + for (; ; ) { + // 获取栈顶节点 + Node t = top.get(); + if (t == null) { + return null; + } + + Node next = t.next; + // top指向栈顶的next + if (top.compareAndSet(t, next)) { + t.next = null; // next清空防止外面直接操作栈 + return t; + } + } + } + + public void push(Node node) { + for (; ; ) { + // 获取栈顶节点 + Node t = top.get(); + + node.next = t; + // 更新top指向node节点 + if (top.compareAndSet(t, node)) { + return; + } + + } + } + } + + public static void testStack() { + // 初始化栈top->1->2->3 + Stack stack = new Stack(); + stack.push(new Stack.Node(3)); + stack.push(new Stack.Node(2)); + stack.push(new Stack.Node(1)); + + new Thread(() ->{ + // 线程1出栈一个元素 + stack.pop(); + }).start(); + + new Thread(() ->{ + // 线程2出栈两个元素 + Stack.Node A = stack.pop(); + Stack.Node B = stack.pop(); + // 线程2入栈A元素 + stack.push(A); + }).start(); + + } + + public static void main(String[] args) { + testStack(); + } +} +``` + +初始化栈top->1->2->3 + +1).线程1出栈,在top.compareAndSet(t, next)暂停,未弹出节点1 + +2).线程2,出栈,弹出节点1,栈结构变为top->2->3 + +3).线程2,出栈,弹出节点2,栈结构变为top->3 + +4).线程2,入栈,添加节点1,栈结构变为top->1->3 + +5).线程1,弹出节点1,结果top->2 + +竟然不是top->3? + +因为线程1在第一步保持的next时节点2,所以它执行成功后top节点就指向节点2了 + +## ABA解决方法 + +1.版本号 + +2.不重复使用节点的引用。如上述节点1 + +3.直接操作元素而不是节点。 + +## AtomicStampedReference源码 + +### 内部类 + +```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); + } + } +``` + +### 属性 + +```java +private volatile Pair pair; +``` + +### 构造器 + +```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); + } +``` + +### 改造ABA问题例子 + +```java +public class ABATest2 { + public static void main(String[] args) { + AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(1,1); + new Thread(() -> { + int[] stampHolder = new int[1]; + int value = atomicStampedReference.get(stampHolder); + int stamp = stampHolder[0]; + System.out.println("Thread 1 read value:"+value); + System.out.println("Thread 1 read stamp:"+stamp); + + LockSupport.parkNanos(100000000L); + + value = atomicStampedReference.get(stampHolder); + stamp = stampHolder[0]; + System.out.println("After parking,Thread 1 read value:"+value); + System.out.println("Thread 1 read stamp:"+stamp); // 版本号变为3了 + if (atomicStampedReference.compareAndSet(value,3,stamp,stamp+1)) { + System.out.println("Thread 1 update from " + value + " to 3"); + } else { + System.out.println("Thread 1 update fail !"); + } + }).start(); + + new Thread(() -> { + int[] stampHolder = new int[1]; + int value = atomicStampedReference.get(stampHolder); + int stamp = stampHolder[0]; + System.out.println("Thread 2 read value:" + value); + System.out.println("Thread 2 read stamp:"+stamp); + + if (atomicStampedReference.compareAndSet(value,2,stamp,stamp+1)) { + System.out.println("Thread 2 update from "+value+" to 2"); + } + + value = atomicStampedReference.get(stampHolder); + stamp = stampHolder[0]; + System.out.println("Thread 2 read value:"+value); + System.out.println("Thread 2 read stamp:"+stamp); + if (atomicStampedReference.compareAndSet(value,3,stamp,stamp+1)) { + System.out.println("Thread 2 update from "+value+" to 3"); + } + }).start(); + } +} +``` + +结果: + +```java +Thread 1 read value:1 +Thread 1 read stamp:1 +Thread 2 read value:1 +Thread 2 read stamp:1 +Thread 2 update from 1 to 2 +Thread 2 read value:2 +Thread 2 read stamp:2 +Thread 2 update from 2 to 3 +After parking,Thread 1 read value:3 +Thread 1 read stamp:3 +Thread 1 update from 3 to 3 +``` + + + + + diff --git a/week_02/08/AtomicStampedReference-008.xmind b/week_02/08/AtomicStampedReference-008.xmind new file mode 100644 index 0000000000000000000000000000000000000000..546f7251ef8d3f5564076e55ad4e55680a092981 Binary files /dev/null and b/week_02/08/AtomicStampedReference-008.xmind differ diff --git a/week_02/08/Unsafe-008.md b/week_02/08/Unsafe-008.md new file mode 100644 index 0000000000000000000000000000000000000000..3febcfd6e092d41f147e5bd476af949fdeed3a79 --- /dev/null +++ b/week_02/08/Unsafe-008.md @@ -0,0 +1,308 @@ +# 读源码--Unsafe(确实不懂,将彤哥的文章里的demo跑了一遍) + +## 获取unsafe的实例 + +### 源码获取实例 + +因为安全问题,getUnsafe方法只允许虚拟机内部调用,否则会抛出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) throws NoSuchFieldException, IllegalAccessException, InstantiationException { + // 源码中theUnsafe字段的类型就是Unsafe + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + Unsafe unsafe = (Unsafe) field.get(null); + } +``` + +## 通过Unsafe实例化类 + +又增加了一种实例化类的方法,尽管应该不常用 + +```java +@Data +public class User { + private int age; + public User() { + this.age = 10; + } + + public int getAge() { + return this.age; + } +} + + User user = new User(); + System.out.println(user.age); + + //通过Unsafe的实例来实例化User类 + User user1 = (User) unsafe.allocateInstance(User.class); + System.out.println(user1.age); + +``` + +## 修改私有字段的值 + +通常情况下私有字段的值是不允许外部更改的,但Unsafe可以(反射大法也行) + +```java + User user2 = new User(); + Field age = user2.getClass().getDeclaredField("age"); + // objectFieldOffset获取字段的偏移值 + unsafe.putInt(user2,unsafe.objectFieldOffset(age),20); + System.out.println(user2.getAge()); +``` + +put系列方法 + +```java + /** + * 给字段赋值 + * @param var1 对象 + * @param var2 字段的偏移值 + * @param var4 新值 + */ + public native void putInt1(Object var1, long var2, int var4); +``` + + + +## 抛出checked异常 + +使用unsafe抛出异常时可以不用在方法签名上定义(有什么用?) + +```java + public static void readFileUnsafe() { + unsafe.throwException(new IOException()); + } +``` + +## 使用堆外内存 + +如果程序在运行过程中,内存不足,会频繁进行GC。此时可以考虑堆外内存,此内存不受jvm控制,使用完毕须释放 + +```java +public class OffHeapArray { + private static final int INT = 4; + private long size; + private long address; + + private static Unsafe unsafe; + // 静态块获取Unsafe实例 + static { + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = (Unsafe) field.get(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + // 构造方法,分配内存 + public OffHeapArray(long size) { + this.size = size; + address = unsafe.allocateMemory(size*INT); + } + + public int get(long i) { + return unsafe.getInt(address+i*INT); + } + + public void set(long i, int value) { + unsafe.putInt(address + i * INT, value); + } + + public long size() { + return size; + } + + public void freeMemory() { + unsafe.freeMemory(address); + } + + public static void main(String[] args) { + OffHeapArray offHeapArray = new OffHeapArray(4); + offHeapArray.set(0, 1); + offHeapArray.set(1, 2); + offHeapArray.set(2, 3); + offHeapArray.set(3, 4); + offHeapArray.set(2,5); + + int sum = 0; + for (int i = 0; i < offHeapArray.size(); i++) { + sum += offHeapArray.get(i); + } + System.out.println(sum); //12:1+2+4+5 + offHeapArray.freeMemory(); // 释放内存 + } + +} +``` + +## CompareAndSwap操作 + +暂时没接触也没用过,但感觉很厉害的样子。留个印象,运用与无锁算法,与java的悲观锁性比,它可利用CAS处理器提供极大的加速。 + +```java +public class Counter { + private volatile int count = 0; + private static long offset; + private static Unsafe unsafe; + + { + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = (Unsafe) field.get(null); + // 获取count字段的偏移地址,以便修改它 + offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count")); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * 对比并替换字段的值 + * @param obj 实例 + * @param offset 字段的偏移地址 + * @param expect 期望值 + * @param update 更新值 + * @return 若期望值与字段当前值相等,则更新;否则不更新 + */ + public native boolean compareAndSwapInt(Object obj, long offset,int expect, int update); + + public void increment() { + int before = count; + // compareAndSwapInt中before的 + // before被赋于count的值后,若其他线程未对count做出修改,则此时before值与count当前值相同,则更新;否则befere重新赋值,直至更新成功 + while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) { + before = count; + } + } + + public int getCount() { + return count; + } + + public static void main(String[] args) throws InterruptedException { + Counter counter = new Counter(); + ExecutorService threadPool = Executors.newFixedThreadPool(100); // 线程池 + + // 100个线程各自增10000次 + IntStream.range(0,100) + .forEach(i->threadPool.submit(()->IntStream.range(0,10000) + .forEach(j->counter.increment()))); + + threadPool.shutdown(); + Thread.sleep(2000); + + System.out.println(counter.getCount()); //1000,000 + + } +} +``` + +## park/unpark + +当一个线程在等待某个操作时,JVM调用Unsafe的park方法来阻塞此线程。 + +当阻塞中的线程需要再次执行时,JVM调用Unsafe的unpark方法来唤醒此线程。 + +## 面试积累一波 + +实例化6大法 + +```java +public class InstantialTest { + private static Unsafe unsafe; + { + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = (Unsafe) field.get(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + + static class User implements Cloneable, Serializable { + private int age; + + public User() { + this.age = 10; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + private static User unSerializable(User user) throws IOException, ClassNotFoundException { + ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E://Project//JavaStudy//week_02//08//object.txt")); + outputStream.writeObject(user); + outputStream.close(); + + ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("E://Project//JavaStudy//week_02//08//object.txt")); + User user1 = (User) inputStream.readObject(); + inputStream.close(); + + return user1; + } + + public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, CloneNotSupportedException, IOException, ClassNotFoundException { + // new + User user = new User(); + + // Class + User user1 = User.class.newInstance(); + + // 反射 + User user2 = User.class.getConstructor().newInstance(); + + // clone + User user3 = (User) user.clone(); + + // 反序列化 + User user4 = unSerializable(user); + + // unsafe + User user5 = (User) unsafe.allocateInstance(User.class); + + System.out.println(user.age); + System.out.println(user1.age); + System.out.println(user2.age); + System.out.println(user3.age); + System.out.println(user4.age); + System.out.println(user5.age); + } +``` + + + + + diff --git a/week_02/08/Unsafe-008.xmind b/week_02/08/Unsafe-008.xmind new file mode 100644 index 0000000000000000000000000000000000000000..966b8747e647177909a04bfa8db5185106cebdfe Binary files /dev/null and b/week_02/08/Unsafe-008.xmind differ diff --git "a/week_02/08/java\345\206\205\345\255\230\346\223\215\344\275\234.png" "b/week_02/08/java\345\206\205\345\255\230\346\223\215\344\275\234.png" new file mode 100644 index 0000000000000000000000000000000000000000..639c7ffd0fc71e276809ea1faae2bac32bb28b92 Binary files /dev/null and "b/week_02/08/java\345\206\205\345\255\230\346\223\215\344\275\234.png" differ diff --git a/week_02/08/object.txt b/week_02/08/object.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed5d92e812e9fb3f9a8f3a2f024a2fb513a1c4c4 Binary files /dev/null and b/week_02/08/object.txt differ