Java原子类与Unsafe

Java原子类与Unsafe

最近了解到CAS(Compare And Swap)算法,比较并交换,意思是当旧值和内存当前值匹配的时候,执行交换,以完成原子操作。现代CPU指令集都支持CAS操作,因此CAS经常用于完成无阻塞算法。

那么问题来了,Java中如何确保原子操作呢?如果使用Java本身的语法,就是加锁了。下面以Counter类为例。

package org.lin.demo.java8;

public class Counter {
    private int value;
    
    //加锁保证原子性
    public synchronized void set(int newValue) {
        value = newValue; 
    }
    
    //模拟CAS
    public synchronized boolean compareAndSet(int expectedValue, int newValue) {
        if (value == expectedValue) {
            value = newValue;
            return true;
        }
        return false;
    }
}
但是,如果使用加锁的方式完成CAS,就没有意义了。因此,Java原子类中大量使用了Unsafe类。

AtomicInteger源码片段如下:
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    /**
     * 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) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
Unsafe类,正如其名,是一个不安全的类。它是用JNI实现的,可以直接操作内存的一个类。可以看到AtomicInteger使用Unsafe完成原子操作,避免使用锁。

如何获取Unsafe实例呢?
1.使用Unsafe的静态工厂方法,Unsafe.getUnsafe(),会抛出安全异常。
jdk执行了权限检查以保证不被信赖的类无法使用Unsafe.

2.使用反射获取Unsafe实例

Unsafe部分源码如下:


/** 
 * This class should provide access to low-level operations and its 
 * use should be limited to trusted code.  Fields can be accessed using 
 * memory addresses, with undefined behaviour occurring if invalid memory 
 * addresses are given. 
 * 这个类提供了一个更底层的操作并且应该在受信任的代码中使用。可以通过内存地址 
 * 存取fields,如果给出的内存地址是无效的那么会有一个不确定的运行表现。 
 * 
 * @author Tom Tromey ([email protected]) 
 * @author Andrew John Hughes ([email protected]) 
 */  
public class Unsafe {  
    // Singleton class.  
    private static Unsafe unsafe = new Unsafe();  
  
    /** 
     * Private default constructor to prevent creation of an arbitrary 
     * number of instances. 
     * 使用私有默认构造器防止创建多个实例 
     */  
    private Unsafe() {  
    }  
  
    /** 
     * Retrieve the singleton instance of <code>Unsafe</code>.  The calling 
     * method should guard this instance from untrusted code, as it provides 
     * access to low-level operations such as direct memory access. 
     * 获取<code>Unsafe</code>的单例,这个方法调用应该防止在不可信的代码中实例, 
     * 因为unsafe类提供了一个低级别的操作,例如直接内存存取。 
     * 
     * @throws SecurityException if a security manager exists and prevents 
     *                           access to the system properties. 
     *                           如果安全管理器不存在或者禁止访问系统属性 
     */  
    public static Unsafe getUnsafe() {  
        SecurityManager sm = System.getSecurityManager();  
        if (sm != null)  
            sm.checkPropertiesAccess();  
        return unsafe;  
    }  }
可以看到Unsafe类有一个static的字段,通过反射,我们可以绕开限制,获得Unsafe实例,例如:
package org.lin.demo.java8;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

public class MyUnsafe {
    
    int num;
    
    public static void main(String[] args) throws Exception {
        
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe)theUnsafe.get(null);
        
        MyUnsafe my = new MyUnsafe();
        long offset = unsafe.objectFieldOffset(MyUnsafe.class.getDeclaredField("num"));
        System.out.println("unsafe = " + offset);
        
        unsafe.getAndSetInt(my, offset, 1);
        printNum(my);
        unsafe.compareAndSwapInt(my, offset, 0, 2);
        printNum(my);
        unsafe.compareAndSwapInt(my, offset, 1, 5);
        printNum(my);
    }
    
    private static void printNum(MyUnsafe my) {
        System.out.println("num = " + my.num);
    }
}
     由于Unsafe的很多操作,都是直接操作内存,所以获取要操作的字段在内存中的偏移量,是使用Unsafe的第一步。Unsafe的其他使用方法可以参考API文档(到网上去找,jdk隐藏了源码)。




猜你喜欢

转载自blog.csdn.net/qq_21508059/article/details/79034381