天天看点

CAS与Unsafe

CAS

操作系统实现锁的方式:

          1.对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位。

          2.多处理器,利用test_and_set,Compare_and_swap指令实现进程互斥.

                   因此,CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。所以,当多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致的情况是不存在的。

#define LOCKED 1
int TestAndSet(int* lockPtr) {//传入一地址
     int oldValue;//声明一变量 
     oldValue = *lockPtr;//把变量设置为 lockPtr地址所指往的值
    *lockPtr = LOCKED;//把原变量设为1
     return oldValue;//返回旧值
}


volatile int lock = 0;

void Critical() {
     while (TestAndSet(&lock) == 1);
     //to do 我们要写的业务代码
     lock = 0 //解放我们的锁 
}


现在线程1,2调用Critical,因为test_and_set 是指令实现为原子性。
假设如果 线程1先进入,  lock=1并反回 0; 然后执行业务代码,然后线程2进入,lock=1,返回1,继续自旋。直到1将锁释放。
Compare_and_swap 则是对test_and_set升级。因为 test_and_set 的 LOCKED 已经定死了。
int compare_and_swap(int* reg, int oldval, int newval)

意思是 oldval和你期望值是否相等,如果相等则设值为newval,并返回老值。 
           

CAS的缺点:

CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

    1.循环时间长开销很大。

          如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

         (可以试2次CAS,然后挂起线程解决)

    2.只能保证一个共享变量的原子操作。

         当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

        (可以,用一个CAS来获取共享变量的操作权限,从而达到控制多个变量的效果)

     3.ABA问题。

            如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。

        (Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。)

Unsafe

Unsafe类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于jdk给java开的一个后门,基于该类可以直接操作一个特定内存的数据。

1.  通过Unsafe类可以分配内存,可以释放内存

public native long allocateMemory(long l);
public native long reallocateMemory(long l, long l1);
public native void freeMemory(long l);
           

2. 可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;

// JAVA中对象的字段的定位可能通过staticFieldOffset方法实现,该方法返回给定field的内存地址偏移量
    public native long staticFieldOffset(Field field);
    // 获取对象中offset偏移地址对应的整型field的值
    public native int getIntVolatile(Object obj, long l);
    // 获取对象中offset偏移地址对应的long型field的值
    public native long getLong(Object obj, long l);
    public native int arrayBaseOffset(Class class1);
    public native int arrayIndexScale(Class class1);
           

   对以上方法不太理解的可以参考《Java对象内存表示机制》

3. 线程挂起与恢复

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

// 是对unsafe线程阻塞方法包装工具类
// 所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法
public class LockSupport {
    public static void unpark(Thread thread) {
        if (thread != null)
            unsafe.unpark(thread);
    }

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(false, 0L);
        setBlocker(t, null);
    }

    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            unsafe.park(false, nanos);
            setBlocker(t, null);
        }
    }

    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(true, deadline);
        setBlocker(t, null);
    }

    public static void park() {
        unsafe.park(false, 0L);
    }

    public static void parkNanos(long nanos) {
        if (nanos > 0)
            unsafe.park(false, nanos);
    }

    public static void parkUntil(long deadline) {
        unsafe.park(true, deadline);
    }
}
           

4. CAS操作

/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
* 
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
           

CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。

主要参考

《Java中Unsafe类详解》