java并发之cas(无锁,自旋)


java并发之cas(无锁,自旋)

JDK5之前都是通过synchronized这种悲观锁的形式,其它线程竞争时所有需要锁的线程挂起,等待持有锁的线程释放锁,相当耗资源。

锁机制存在以下问题:

(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。

(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

后面引入了cas(compare and swap),无锁,自旋。

什么是无锁?顾名思义,就是没有锁,不需要通过synchronized关键字修饰;

什么是自旋?自旋这一概念是在从锁升级上提出来的,锁升级步骤如下:

第零级:无锁状态

第一级:偏向锁,第一个线程通过synchronized(Object)在jvm上并没有加上锁,只是标记了该object上的线程ID,这个时候为偏向锁;

(当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。)

第二级:自选锁(轻量级锁),线程通过synchronized(Object),当其他线程还没有是否释放,那么该线程会自旋等待,类似while(true),默认超过十次就会升级为下一层级;

第三级:自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。

 

乐观锁:CAS,Compare and Swap

cas是一种用于在多线程环境下实现同步功能的机制。CAS 操作包含三个操作数 -- 内存位置、预期数值和新值。CAS 的实现逻辑是将内存位置处的数值与预期数值想比较,若相等,则将内存位置处的值替换为新值。若不相等,则不做任何操作。

case (v,excepted,new)
if(v == excepted){
//传入值和期望值相等
v=new
}else{
//try again or fail
}

这里引入其他博主的cas远离做补充

https://blog.csdn.net/u011506543/article/details/82392338

CAS看起很爽,但是会出现ABA问题

ABA问题

CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。

解决办法:  用AtomicStampedReference/AtomicMarkableReference解决ABA问题,实质也是通过stamp来做进一步校验,类似version乐观锁来判断。

//参数代表的含义分别是 期望值,写入的新值,期望标记,新标记值
public boolean compareAndSet(V expected,V newReference,int expectedStamp,int newStamp);

public V getRerference();

public int getStamp();

public void set(V newReference,int newStamp);
import java.util.concurrent.atomic.AtomicStampedReference;

public class Test {

    public static void main(String[] args) {
        // https://my.oschina.net/senman/blog/3215704/print
        Counter count = new Counter();
        count.increment();
        count.increment();
        System.out.println(count.getCount());
        count.decrement();
        System.out.println(count.getCount());
    }

}

class Counter {
    private AtomicStampedReference<Integer> count = new AtomicStampedReference<Integer>(0, 0);

    public int getCount() {
        return count.getReference();
    }

    public int increment() {
        int[] stamp = new int[1];
        while (true) {
            Integer value = count.get(stamp); // 同时获取时间戳和数据,防止获取到数据和版本不是一致的
            int newValue = value + 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp[0], stamp[0] + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }

    public int decrement() {
        int[] stamp = new int[1];
        while (true) {
            Integer value = count.get(stamp);// 同时获取时间戳和数据,防止获取到数据和版本不是一致的
            int newValue = value - 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp[0], stamp[0] + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }
}

class Count2 {
    private AtomicStampedReference<Integer> count = new AtomicStampedReference<Integer>(0, 0);

    public int getCount() {
        return count.getReference();
    }

    public int increment() {
        while (true) {
            // 必须先获取stamp,然后取值,顺序不能反,否则仍然会有ABA的问题
            int stamp = count.getStamp();
            Integer value = count.getReference();
            int newValue = value + 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp, stamp + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }

    public int decrement() {
        while (true) {
            // 必须先获取stamp,然后取值,顺序不能反,否则仍然会有ABA的问题
            int stamp = count.getStamp();
            Integer value = count.getReference();
            int newValue = value - 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp, stamp + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }
}

也可以通过原子类来声明变量

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM