JavaSE-多线程(4)- CAS

JavaSE-多线程(4)- CAS

CAS (CompareAndSwap)举例

例1:在此例中,有 THREAD_COUNT (10)个数量的线程调用 AtomicTest 对象m方法,对属性count进行自增操作,最后在主线程打印count的最终结果为10,由于存在多线程多并发访问count,所以在自增操作前使用synchronized关键字对AtomicTest对象进行了锁定。

package com.hs.example.base.multithread.day01;

public class AtomicTest {

    private volatile int count = 0;

    public void m() {
        System.out.println(Thread.currentThread().getName() + " m start...");
        try {
            //模拟业务代码所消耗的时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m count...");
        synchronized (this) {
            count++;
        }
        System.out.println(Thread.currentThread().getName() + " m end...");
    }

    public static void main(String[] args) {
        final int THREAD_COUNT = 10;
        AtomicTest atomicTest = new AtomicTest();
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(atomicTest::m);
            threads[i].setName("线程" + i);
        }
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i].start();
        }
        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread t = threads[i];
            try {
                System.out.println(t.getName() + " 准备join");
                // join 方法只能保证这 THREAD_COUNT 个线程在 main 线程前执行完,并不能保证这 THREAD_COUNT 个线程的顺序,
                // 除非在 threads[i].start(); 后 立即调用 threads[i].join(); 才可 保证 THREAD_COUNT 个线程按照顺序执行
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(atomicTest.count);
    }
}
线程2 m start...
线程0 m start...
线程1 m start...
线程3 m start...
线程0 准备join
线程8 m start...
线程7 m start...
线程4 m start...
线程5 m start...
线程6 m start...
线程9 m start...
线程2 m count...
线程1 m count...
线程1 m end...
线程2 m end...
线程5 m count...
线程5 m end...
线程6 m count...
线程9 m count...
线程6 m end...
线程9 m end...
线程0 m count...
线程0 m end...
线程1 准备join
线程2 准备join
线程4 m count...
线程4 m end...
线程7 m count...
线程7 m end...
线程8 m count...
线程8 m end...
线程3 准备join
线程3 m count...
线程3 m end...
线程4 准备join
线程5 准备join
线程6 准备join
线程7 准备join
线程8 准备join
线程9 准备join
10

synchronized可以保证线程安全性,上例是多线程并发访问同一变量的常规写法,但是不用synchronized是否可以达到同样的效果呢,请看例2:
例2

package com.hs.example.base.multithread.day01;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicTest2 {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public void m() {
        System.out.println(Thread.currentThread().getName()+" m start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" m count...");
        atomicInteger.incrementAndGet();
        System.out.println(Thread.currentThread().getName()+" m end...");
    }

    public static void main(String[] args) {
        final int THREAD_COUNT = 20;
        AtomicTest2 atomicTest = new AtomicTest2();
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(atomicTest::m);
        }
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i].start();
        }
        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread t = threads[i];
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(atomicTest.atomicInteger.get());
    }
}

Thread-4 m start...
Thread-8 m start...
Thread-3 m start...
Thread-2 m start...
Thread-0 m start...
Thread-1 m start...
Thread-5 m start...
Thread-7 m start...
Thread-9 m start...
Thread-13 m start...
Thread-6 m start...
Thread-14 m start...
Thread-12 m start...
Thread-15 m start...
Thread-11 m start...
Thread-10 m start...
Thread-17 m start...
Thread-18 m start...
Thread-16 m start...
Thread-19 m start...
Thread-8 m count...
Thread-8 m end...
Thread-3 m count...
Thread-3 m end...
Thread-4 m count...
Thread-2 m count...
Thread-2 m end...
Thread-4 m end...
Thread-18 m count...
Thread-6 m count...
Thread-6 m end...
Thread-18 m end...
Thread-0 m count...
Thread-0 m end...
Thread-9 m count...
Thread-13 m count...
Thread-17 m count...
Thread-17 m end...
Thread-1 m count...
Thread-5 m count...
Thread-5 m end...
Thread-7 m count...
Thread-10 m count...
Thread-10 m end...
Thread-11 m count...
Thread-12 m count...
Thread-12 m end...
Thread-14 m count...
Thread-15 m count...
Thread-15 m end...
Thread-19 m count...
Thread-19 m end...
Thread-16 m count...
Thread-16 m end...
Thread-14 m end...
Thread-11 m end...
Thread-7 m end...
Thread-1 m end...
Thread-13 m end...
Thread-9 m end...
20
incrementAndGet 源码
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

sun.misc.Unsafe 对象 getAndAddInt 方法

/**
 * var1:对象
 * var2:偏移量
 * var4:偏移量对应的属性值需要增加的数值
 */
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // 获取最新的值
        var5 = this.getIntVolatile(var1, var2);
        // 调用 compareAndSwapInt 本地方法尝试更新值为 var5 + var4,如果更新失败就循环当前操作(自旋)
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

内部调用native本地方法

/**
 * var1:对象
 * var2:偏移量
 * var4:预期值
 * var5:期望更新的值
 * 此方法通过偏移量获取对象当前值,使用当前值与预期值进行判断,如果相等则将值更新为期望值,否则更新失败
 */
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
CAS 解释

cas(v,expected,newValue)
v : 当前值
expected : 预期的值
newValue : 设置的新值
if( v == expected){
v = newValue ;
}else{
try again or fail
}
使用cas方式给变量赋值时,首先比对变量当前值和预期值是否一致,一致才赋值新的值,否则重新自旋,
由于cas操作属于硬件同步原语,cpu提供了基本内存操作的原子性保证,所以不用担心两个线程同时判断 v 和 expected 相等的问题

CAS 存在的问题

ABA问题: 假设有两个线程同时访问某个变量 N,N的初始值为A,线程2读取N的值为A,准备更改这个值,但是在线程2修改之前,线程1 将 N 的值设置为B,然后又将 B 设置成A,线程2更新值时,没有感知N的值的变化,判断N的预期值与当前值相等,任然可以成功更新N的值。
如果不关心数据变化过程,上面的例子也没问题,否则就要重新思考数据相等的判断依据。

CAS 如何解决ABA问题

加 version
上例中每对N进行修改的时候都加一个版本号,例如 1A 2B 3A,判断的时候同时使用值和版本号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值