• 1. AtomicInteger 、Unsafe 及 CAS方法的整理


    本文摘自:  

      https://blog.csdn.net/fanrenxiang/article/details/80623884

      http://ifeve.com/sun-misc-unsafe/

      https://blog.csdn.net/likailonghaha/article/details/70156858

      

    一、前言

     1、为什么需要AtomicInteger原子操作类?

      对于全局变量的数值类型作 count++,若没有加 synchronized 关键字则是线程不安全的,count++ 解析为 :

      ①、读取 count 值 ②、赋值 count = count + 1;显然,这个操作不具备原子性,多线程时会出现问题,如下测试:

     1     public static int count = 0;
     2 
     3     public static void main(String[] args) {
     4         for(int i = 0; i < 100; i++) {
     5             new Thread() {
     6                 public void run() {
     7                     count++;
     8                 }
     9             }.start();
    10          }
    11         System.out.println("count: " + count);
    12     }

    输出的结果为count: 94,这个值不定,每次测试都可能不一样,很显然,100个线程跑++操作,结果并没有像预期的那样count: 100。

      2、要是换成volatile修饰count变量呢?

      volatile 修饰的变量能够在线程之间保持可见性,能被多个线程同时读但是又能保证只被单线程写,而且不会读到过期值,volatile 修饰字段的写入操作总是优先于读操作,即使多个线程同时修改 volatile 变量字段,总能保证获取到的是最新的值。如下测试:

     1     public  static  volatile  int count = 0;
     2 
     3     public static void main(String[] args) throws InterruptedException {
     4         for (int i = 0; i < 100; i++) {
     5             new Thread() {
     6                 public void run() {
     7                     for (int j = 0; j < 100; j++) {
     8                         count++;
     9                     }
    10                 }
    11             }.start();
    12         }
    13         Thread.sleep(1000);
    14         System.out.println("volatile count: " + count);
    15 
    16     }

    结果似乎又失望了,出现volatile count: 9992,果然还是出现问题了,volatile仅仅保证变量在线程间保持可见性,却依然不能保证非原子性的操作。

      3、用了AtomicInteger类后会变成什么样子呢?

    public class AtomicIntegerTest2 {
     
        public static AtomicInteger count = new AtomicInteger(0);
     
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 100; i++) {
                new Thread() {
                    public void run() {
                        for (int j = 0; j < 100; j++) {
                            count.getAndIncrement();
                        }
                    }
                }.start();
            }
            Thread.sleep(1000);
            System.out.println("AtomicInteger count: " + count);
        }
    }

    结果每次都输出"AtomicInteger count: 10000",没毛病。

    二、AtomicInteger、Unsafe 类介绍

      1、AtomicInteger 

        来自 java.util.concurrent.atomic 包,atomic包下提供AtomicBoolean/AtomicLong/AtomicInteger三个原子更新基本类型,以AtomicInteger为例,其他两种基本类似。以下是AtomicInteger囊括的大致方法

        //给AtomicInteger设置newValue并返回加oldValue
        public final int getAndSet(int newValue)       
        
        //如果输入的值和期望值相等就set并返回true/false
        public final boolean compareAndSet(int expect, int update)    
        
        //对AtomicInteger原子的加1并返回当前自增前的value
        public final int getAndIncrement()     
        
        //对AtomicInteger原子的减1并返回自减之前的的value
        public final int getAndDecrement()   
        
        //对AtomicInteger原子的加上delta值并返加之前的value
        public final int getAndAdd(int delta)   
        
        //对AtomicInteger原子的加1并返回加1后的值
        public final int incrementAndGet()   
        
        //对AtomicInteger原子的减1并返回减1后的值
        public final int decrementAndGet()    
        
        //给AtomicInteger原子的加上指定的delta值并返回加后的值
        public final int addAndGet(int delta)   

    以getAndIncrement为例看下源码

    public final int getAndIncrement() {
        for (;;) {  // 若 compareAndSet 为 false, 则一直循环查询
            //先取出AtomicInteger的当前值
            int current = get();
            //对当前值加1操作
            int next = current + 1;
            //这里很关键,通过compareAndSet方法比较当前值有没有被其它线程修改
            if (compareAndSet(current, next))
                return current;
        }
    }

    compareAndSet方法里面是调用了Unsafe类的compareAndSwapInt方法(后边讨论)

    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    以下先介绍 Unsafe 类:


    2、Unsafe


      作用:Unsafe类提供了硬件级别的原子操作,提高了Java对底层操作的能力。
      来自 sun.misc 包,Java最初被设计为一种安全的受控环境,但是 sun.misc.Unsafe 是 Java HotSpot 提供的操作内存和线程的"后门",提供了一些可以直接操控内存和线程的底层操作。
      Unsafe被JDK广泛应用于 java.nio 和并发包等实现中,这个不安全的类提供了一个观察 HotSpot JVM 内部结构并且可以对其进行修改,但是不建议在生产环境中使用

      先看部分 Unsafe 源码:
        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、获取Unsafe实例 

    • 不能直接new Unsafe(),原因是Unsafe被设计成单例模式,构造方法是私有的;
    • 不能通过调用Unsafe.getUnsafe()获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载

      Unsafe 实例获取方法:

    //方法一:我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath选项,指定系统类路径加上你使用的一个Unsafe路径(但这太难了。)
    java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
    
    // 方法二: 使用反射,Unsafe类包含一个私有的、名为theUnsafe的实例,我们可以通过Java反射窃取该变量。
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }

      

      2、Unsafe API

      sun.misc.Unsafe类包含105个方法。实际上,对各种实体操作有几组重要方法,其中的一些如下:

    Info.仅返回一些低级的内存信息

    • addressSize
    • pageSize

    Objects.提供用于操作对象及其字段的方法

    • allocateInstance
    • objectFieldOffset

    Classes.提供用于操作类及其静态字段的方法

    • staticFieldOffset
    • defineClass
    • defineAnonymousClass
    • ensureClassInitialized

    Arrays.操作数组

    • arrayBaseOffset
    • arrayIndexScale

    Synchronization.低级的同步原语

    • monitorEnter
    • tryMonitorEnter
    • monitorExit
    • compareAndSwapInt
    • putOrderedInt

    Memory.直接内存访问方法

    • allocateMemory
    • copyMemory
    • freeMemory
    • getAddress
    • getInt
    • putInt

    3、有趣的用例

      ①、避免初始化

       当你想要跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类,allocateInstance方法是非常有用的。考虑以下类:

    class A {
        private long a; // not initialized value
    
        public A() {
            this.a = 1; // initialization
        }
    
        public long a() { return this.a; }
    }

    使用构造器、反射和unsafe初始化它,将得到不同的结果。

    A o1 = new A(); // constructor
    o1.a(); // prints 1
    
    A o2 = A.class.newInstance(); // reflection
    o2.a(); // prints 1
    
    A o3 = (A) unsafe.allocateInstance(A.class); // unsafe
    o3.a(); // prints 0

      

    Unsafe 的 allocateInstance 方法可以绕过构造方法直接创建对象

    objectFieldOffset 方法可以为对象属性赋值。

        public static void main(String[] args) {
            try {
                Customer customer = (Customer) UNSAFE.allocateInstance(Customer.class);
                customer.setName("King");
                System.out.println(customer.toString());
                
                Field name = customer.getClass().getDeclaredField("name");
                UNSAFE.putObject(customer, UNSAFE.objectFieldOffset(name), "LoLa");
                System.out.println(customer.toString());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

      ②、内存崩溃(Memory corruption)

      这对于每个C程序员来说是常见的。顺便说一下,它是绕过安全的常用技术。

      考虑下那些用于检查“访问规则”的简单类:

    class Guard {
           private int ACCESS_ALLOWED = 1;
    
           public boolean giveAccess() {
                  return 42 == ACCESS_ALLOWED;
           }
    }

      客户端代码是非常安全的,并且通过调用giveAccess()来检查访问规则。可惜,对于客户,它总是返回false。只有特权用户可以以某种方式改变ACCESS_ALLOWED常量的值并且得到访问

      实际上,这并不是真的。演示代码如下:

    Guard guard = new Guard();
    guard.giveAccess();   // false, no access
    
    // bypass
    Unsafe unsafe = getUnsafe();
    Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
    unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
    
    guard.giveAccess(); // true, access granted

      ③、并发(Concurrency)

      compareAndSwap 方法是原子的,并且可用来实现高性能的、无锁的数据结构。

      比如,考虑问题:在使用大量线程的共享对象上增长值。

      

      首先,我们定义简单的Counter接口:

      

    interface Counter {
        void increment();
        long getCounter();
    }

     然后,我们定义使用Counter的工作线程CounterClient

    class CounterClient implements Runnable {
        private Counter c;
        private int num;
    
        public CounterClient(Counter c, int num) {
            this.c = c;
            this.num = num;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < num; i++) {
                c.increment();
            }
        }
    }

    测试代码:

    int NUM_OF_THREADS = 1000;
    int NUM_OF_INCREMENTS = 100000;
    ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);
    Counter counter = ... // creating instance of specific counter
    long before = System.currentTimeMillis();
    for (int i = 0; i < NUM_OF_THREADS; i++) {
        service.submit(new CounterClient(counter, NUM_OF_INCREMENTS));
    }
    service.shutdown();
    service.awaitTermination(1, TimeUnit.MINUTES);
    long after = System.currentTimeMillis();
    System.out.println("Counter result: " + c.getCounter());
    System.out.println("Time passed in ms:" + (after - before));

    第一个无锁版本的计数器:

    运行快,但没有线程管理,结果是不准确的。

    class StupidCounter implements Counter {
        private long counter = 0;
    
        @Override
        public void increment() {
            counter++;
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }

    输出:

    Counter result: 99542945
    Time passed in ms: 679

    第二次尝试,添加上最简单的java式同步:

    激进的同步有效,但耗时长。

    class SyncCounter implements Counter {
        private long counter = 0;
    
        @Override
        public synchronized void increment() {
            counter++;
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }

    输出:

    Counter result: 100000000
    Time passed in ms: 10136

    第三种,试试ReentrantReadWriteLock

    仍然正确,耗时较短。

    class LockCounter implements Counter {
        private long counter = 0;
        private WriteLock lock = new ReentrantReadWriteLock().writeLock();
    
        @Override
        public void increment() {
            lock.lock();
            counter++;
            lock.unlock();
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }

    第四种,atomics的运行效果如何?

    AtomicCounter的运行结果更好。

    class AtomicCounter implements Counter {
        AtomicLong counter = new AtomicLong(0);
    
        @Override
        public void increment() {
            counter.incrementAndGet();
        }
    
        @Override
        public long getCounter() {
            return counter.get();
        }
    }

    第五种,试试Unsafe原始的compareAndSwapLong,看看它是否真的只有特权才能使用它?

    class CASCounter implements Counter {
        private volatile long counter = 0;
        private Unsafe unsafe;
        private long offset;
    
        public CASCounter() throws Exception {
            unsafe = getUnsafe();
            offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
        }
    
        @Override
        public void increment() {
            long before = counter;
            while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
                before = counter;
            }
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }

    第四种、第五种时间大致一样,Unsafe 看起来似乎等价于atomics。atomics使用Unsafe?(是的)

    3、 CAS

      1、CAS介绍

        AtomicInteger的核心就是一个CAS算法(CompareAndSwap),比较并交换算法,此算法是由unsafe的底层代码实现,它是一个原子的操作。 (CAS能很高效的解决原子操作)

        原理就是:如果内存中的实际值与update值相同,则将实际值更新为expect值,反之则返回失败,由上层系统循环获取实际值后,再次调用此CAS算法。

        

        CAS 是设计并发算法时常用到的一种技术,java.util.concurrent包完全建立在CAS之上。

        CAS是通过Unsafe实现的,java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。

        看下Unsafe下的三个方法:

    public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
    
    public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
    
    public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

      

      2、CAS 在 Unsafe 中应用

      以  compareAndSwapInt 为例, CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

      unsafe.compareAndSwapInt(this, this, valueOffset, expect, update),包含 ①比较②更新两个操作,compareAndSwapInt如何这两个步骤的原子性呢? 

      

      3、CAS原理

       CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用(用 native 关键字标注),允许java调用其他语言。

       而compareAndSwapInt就是借助C来调用CPU底层指令实现的。

    三、总结

      这是我认真看了一天博客摘抄下来的整理,我要保持每天不断的学习,争取早日写出有水平的干货

  • 相关阅读:
    我的第一个网络爬虫
    python中os模块的walk函数
    <Think Python>中统计文献单词的处理代码
    <Think Python>中斐波那契使用memo实现的章节
    Python中bisect的使用
    vim中使用系统粘贴板
    linux下的重命名
    HTML Dog 初级教程中关于 forms 的翻译
    unittest:1 用例编写
    python UI自动化实战记录十一: 总结
  • 原文地址:https://www.cnblogs.com/skillking/p/9720645.html
Copyright © 2020-2023  润新知