From a64afa4cb7d12b9d70e28780a192e63236a5e72f Mon Sep 17 00:00:00 2001 From: chenjing Date: Sun, 15 Mar 2020 18:02:35 +0800 Subject: [PATCH] 83 --- second/week_02/83/atomic.md | 108 ++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 second/week_02/83/atomic.md diff --git a/second/week_02/83/atomic.md b/second/week_02/83/atomic.md new file mode 100644 index 0000000..aec266a --- /dev/null +++ b/second/week_02/83/atomic.md @@ -0,0 +1,108 @@ +原子操作指不会被线程调度机制打断的操作,原子操作一旦开始就一直运行到结束,中间不会有任何线程上下文切换。 +# unsafe +是系统native提供的一个方法,仅供java核心类库使用。由java中魔法类sun.misc.Unsafe提供 +```java +public static Unsafe getUnsafe(){ + Class var0 = Reflection.getCallerClass(); + if(!VM.isSystemDomainLoader(var0.getClassLoader())){ + throw new SecurityException("Unsafe"); + } else { + return theUnsafe; + } +} +``` +juc中使用的CAS(compare and swap)操作的底层是调用unsafe的comapreAndSwapXXXX()这种方法被广泛用于无锁算法,与java中的悲观锁相比,可以利用处理器指令提供极大的加速 + +## 应用:线程安全计数器 +```java +class Counter{ + /** volatile以便对它的修改所有线程都可见,并在类加载的时候获取count在类中的偏移地址 + **/ + private volatile int count =0; + private static long offset; + private static Unsafe unsafe; + static{ + try{ + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + offset = unsafe.objectFieldOffset(Counter.class.getField("count")); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + public void increment(){ + int before = count; + while(!unsafe.compareAndSwapInt(this,offset,before,before+1)){ + before = count; + } + } + public int getCount(){ + return count; + } +} +``` +unsafe可以实现: +1.实例化一个类。 +2.修改私有字段的值。 +3.抛出checked异常。 +4.使用堆外内存。 +5.CAS操作。 +6.阻塞/唤醒线程 + +# AtomicInteger +是java并发包下面提供的原子类,主要操作的是int类型的整形,通过调用底层Unsafe的CAS等方法实现原子操作 + + +实现原子性原理 +1.AtomicInteger中维护了一个使用volatile修饰的变量value,保证可见性; +2.AtomicInteger中的主要方法最终几乎都会调用到Unsafe的compareAndSwapInt()方法保证对变量修改的原子性 + + + +# AtomicStampedReference +原子更新引用类型,内部用Pair承载引用对象及更新的邮戳,避免了ABA问题。 +## ABA问题 +多线程环境中,当某线程连续读取同一块内存地址两次,两次得到的值一样,然而,同时可能存在另一个线程在这两次读取之间把这个内存地址的值从A修改成了B又修改回了A,这时还简单地认为“没有修改过”显然是错误的。 +解决方法:1.版本号,使用前检查版本号是否变过。2.不重复使用节点引用。3.直接操作元素而不是节点 + +AtomicStampedReference 用Pair类绑定值和版本号 +```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 static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); +private static final long pairOffset = + objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); +``` +声明一个Pair类型的变量并使用Unsfae获取其偏移量,存储到pairOffset中 + + + +#源码测试用例 +```java +private static AtomicInteger counter = new AtomicInteger(0); + public static void increase(){ + counter.getAndIncrement(); + } + public static void main(String[] args){ + IntStream.range(0,20).forEach(x->new Thread(()-> IntStream.range(0,100).forEach(j->increase())).start()); + while (Thread.activeCount()>1){ + Thread.yield(); + } + System.out.println(counter); + } +``` \ No newline at end of file -- Gitee