java 并发 JUC Atomic 二

什么是原子操作

原子操作是指不会被线程调度机制打断的操作,在JAVA中i++ 是原子操作吗?答案是否定否定的。用一段代码做一个测试,count 计数器由于不是原子操作所以会产生无法预料的结果。

    static AtomicInteger atomicCount = new AtomicInteger(0);
    static Integer count = new Integer(0);
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[500];
        for (int i = 0; i < 500; i++) {
            threads[i] =
            new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    atomicCount.addAndGet(1);
                    count++;
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        for (int i = 0; i < 500; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 500; i++) {
            threads[i].join();
        }

        System.out.println("atomicInteger = " + atomicCount.get());
        System.out.println("count = " + count);
    }

走近 atomic*

atomic* 由 Doug Lea (JUC几乎由他一人完成) 大神设计。在上面的测试中,见识了AtomicInteger的魔力,那么不知道你有没有同样的疑问,在多线程中,如果只能保证原子性是否能线程安全呢?在不使用synchronized(使用JVM调用指令保证原子性,优化后的synchronized没有研究过,如果你知道不妨留言告知。) 关键字的情况下又如何保证原子性!如果你看过源码就会发现, atomic* 通过 可见性 + 原子性保证线程安全。

  • 什么是可见性

见性
可见性是指一个线程修改变量后,对其它线程是立即可见的。
java中volatile保证可见性, 但是这并不意味着使用了volatile的变量就是线程安全的。在AtomicInteger中可以看到,他的值是通过 volatile 修饰的。

    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); }
    }
    // volatile  修饰的变量,保证可见性
    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
  • 原子操作
    原子操作开篇已经描述过了,直接进入正题,在atomic*中使用 unsafe是原子操作的核心。unsafe作为BUG级的存在带给我直接操作内存的便利,这点和 C , C++ 的指针相同, 所以并不提倡我们直接使用。在unsafe中compareAndSwapInt(
    CAS更多详细介绍)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

compareAndSwapInt 的逻辑是这样的

function cas(p : pointer to int, old : int, new : int) returns bool {
    if *p ≠ old {
        return false
    }
    *p ← new
    return true
}

p指向的位置,与预期的值比较, 不相等就返回false, 否则返回true 并把新的赋值给老的值。

  • 非阻塞算法 (nonblocking algorithms)
    有了可见性、 原子性, 还是无法保证我们在多线程能够得到预期结果因为在上面代码中compareAndSwapInt尝试一次,就会返回结束。在这里引入(非阻塞算法 (nonblocking algorithms))可以通过代码发现。
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

可以看到当不满足交换条件时,线程会一直阻塞下去, 但是代码级的阻塞不影响其它线程的执行(JVM线程等待后需要被唤醒)从而也会产生系统开销。

AtomicInteger API 以及应用

  • API
    int getAndAdd(int delta) 原子更新并且返回更新前的值
    int addAndGet(int delta) 原子添加并返回当前值.
    int decrementAndGet() 原子减一
    int get() 得到当前值

boolean compareAndSet(int expect, int update) 如果当前值等于期望值,则按原子值将该值设置为给定的更新值。(不会等待)

void lazySet(int newValue) 最终设置为给定值,通过文档翻译过来的,这个方法有些科幻更多参见这里
没有罗列所有API 但是其它的API也大同小异

  • 应用
    Atomic主要应用JUC后续的API中,比如读写锁, 共享锁,信号量。在之后将会更多看到Atomic的身影。自身在应用中没有实际使用过Atomic的API,但是借鉴的意义很大,比如使用Redis做分布式锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值