JUC学习之路(基础类型原子操作类)(二)

7 篇文章 0 订阅
2 篇文章 0 订阅

目录

问题引出

原子操作类

基础类型原子操作类

 AtomicInteger

        范例

        源代码

 AtomicLang

        范例

        源代码

高性能的CAS处理机制


问题引出

我们模拟一个存款操作,代码如下

    private static int money = 0;

    @Test
    public void plus() throws InterruptedException {
        int[] data = new int[] {100,200,300};
        for (int i = 0; i < data.length; i++) {
            final int temp = i;
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                money += data[temp];
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(money);
    }

输出结果:

         此时,这个操作就会出现不同步的设计问题,按照最传统的思路来解决问题,就必须使用同步方法。

    private static int money = 0;

    @Test
    public void plus() throws InterruptedException {
        int[] data = new int[] {100,200,300};
        for (int i = 0; i < data.length; i++) {
            final int temp = i;
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                save(data[temp]);
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("【存入的金额为】"+money);
    }

    public synchronized void save(int m){
        money += m;
    }

 结果

         思考,难道现在做一个普通的数据计算都要考虑到这种同步的处理机制吗?实在太繁琐了。为了解决这样的解决问题,JDK提供了J.U.C的原子操作类,那么下面使用J.U.C的原子类进行同步

原子操作类

        原子操作类并没有使用到传统的同步机制,而是通过一种CAS 的机制来完成的,整个原子类采用了类似的实现机制。

        在java.util.concurrent.atomic 包中提供了多种原子性的操作类支持,这些操作可以分为四类:
                - 基本类型:AtomicInteger、AtomicLong、AtomicBoolean;
                - 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;
                - 引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference;
                - 对象的属性修改类型:AtomicIntegerFieldUpdate、AtomicLongFieldUpdate、AtomicReferenceFieldUpdate。

        原则:所有的原子类都具有同步的支持,但是考虑到性能问题,没有使用到Synchronized 关键字来实现,其依靠底层实现的。

基础类型原子操作类

        基础类型的原子类一共提供了三个:AtomicInteger、AtomicLong、AtomicBoolean,首先我们来观察这些类的集成结构

public class AtomicInteger extends Number implements java.io.Serializable 
public class AtomicLong extends Number implements java.io.Serializable

public class AtomicBoolean implements java.io.Serializable

        其中,AtomicInteger 和 AtomicLong 都属于Number 的子类,而AtomicBoolean 是Object 的子类;

 AtomicInteger

        范例

    // 定义原子操作类
    private static AtomicInteger atomicMoney = new AtomicInteger(0);

  /** 原子操作类 **/
    @Test
    public void plusAtomic() throws InterruptedException {
        int[] data = new int[] {100,200,300};
        for (int i = 0; i < data.length; i++) {
            final int temp = i;
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicMoney.addAndGet(data[temp]);
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("【atomic存入的金额为】"+atomicMoney.get());
    }

        以上的代码,利用原子类解决了 synchronized 同步的设计问题了,但是这个原子类并没有使用传统的同步机制,我们观察它的源代码;

        源代码

package java.util.concurrent.atomic;

import java.lang.invoke.VarHandle;
import java.util.function.IntBinaryOperator;
import java.util.function.IntUnaryOperator;
public class AtomicInteger extends Number implements java.io.Serializable {

    private static final long serialVersionUID = 6214790243416807050L;
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
    private volatile int value;
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }


    public final int addAndGet(int delta) {
        return U.getAndAddInt(this, VALUE, delta) + delta;
    }
}

 AtomicLang

        范例

        我们设置一个初始金额,然后每次增加100

    @Test
    public void testAtomicLang() throws InterruptedException {
        AtomicLong atomicLong = new AtomicLong(0);
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                System.out.printf(
                        "【%s】数据的加法计算:%d %n"
                        ,Thread.currentThread().getName(),atomicLong.addAndGet(100));
            }).start();
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("【计算完成】最终计算结果:"+atomicLong.get());
    }

输出结果

         通过以上的分析对于原子操作类的基本使用已经没有太大的问题了,那么后面需要面对的就是如果分析具体操作的实现,下面来逐步的对当前类的实现源代码进行解析。

        源代码

        1、观察 长整型 的基本定义

package java.util.concurrent.atomic;

import java.lang.invoke.VarHandle;
import java.util.function.LongBinaryOperator;
import java.util.function.LongUnaryOperator;

public class AtomicLong extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 1927816293512124184L;

    /* 我们会看到这里和atomicInteger 有所不同,
       因为Integer 所占的位数为32 为Long所占用的长度是64,
       所以在32位的系统中,需要修改两个32位的内容 */        
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    private static native boolean VMSupportsCS8();

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicLong.class, "value");
    private volatile long value;

    public AtomicLong(long initialValue) {
        value = initialValue;
    }

        Long是属于64位的长度,如果运行在了32位的系统之中,那么就需要有2位去描述long 数据类型,而在进行数据修改的时候必须考虑 2 位 的数据同时修改完成,才可以称为正确的修改。

        2、观察增加方法的实现

    public final long addAndGet(long delta) {
        return U.getAndAddLong(this, VALUE, delta) + delta;
    }

        3、数据的增加操作依靠的是我们的Unsafe类提供的处理方法

    @HotSpotIntrinsicCandidate
    public final long getAndAddLong(Object o, long offset, long delta) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!weakCompareAndSetLong(o, offset, v, v + delta));
        return v;
    }

    /** Volatile version of {@link #getLong(Object, long)}  */
    @HotSpotIntrinsicCandidate
    public native long    getLongVolatile(Object o, long offset);


    @HotSpotIntrinsicCandidate
    public final boolean weakCompareAndSetLong(Object o, long offset,
                                               long expected,
                                               long x) {
        return compareAndSetLong(o, offset, expected, x);
    }

    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetLong(Object o, long offset,
                                                  long expected,
                                                  long x);

        此时的操作方法是由硬件的CPU的指令处理完成的,它不再是通过Java的运行机制来完成的,这样的优势就是在于速度快,同时又避免了数据的内存之中的互相拷贝所带来的额外开销。

        在AtomicLong 类中还提供了一个 “compareAndSet()” 方法,该方法的主要作用是进行数据内容的修改,但是在修改之前,需要首先判断当前所保存的数据是否和内容相同,如果相同,则允许修改,如果不同则不允许修改

        4、观察CAS 方法实现修改操作

   @Test
    public void testCompareAndSet() {
        AtomicLong atomicLong = new AtomicLong(100L);
        System.err.println(
                "【× 原子数据修改】数据修改的结果:" + atomicLong.compareAndSet(200L, 300L));
        System.err.println(
                "【原子数据获取】新的数据内容:" + atomicLong.get());
        System.out.println(
                "【√ 原子数据修改】数据修改的结果:" + atomicLong.compareAndSet(100L, 300L));
        System.out.println(
                "【原子数据获取】新的数据内容:" + atomicLong.get());
    }

        运行结果

         只有在CAS 操作比较成功之后才会进行内容的修改,而如果此时的比较失败是不会修改内容的,这是一种乐观锁的机制。

高性能的CAS处理机制

        compareAndSet() 数据修改操作方法在J.U.C 中被称之为CAS 机制,CAS (Compare-And-Swap) 是一条CPU 并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,反之则不修改,这个过程就是原子性操作。

        在多线程进行数据修改时,为了保证数据修改的正确性,常规的做法就是使用synchronized 同步锁,但是这种锁属于“悲观锁”,每个线程在操作之前锁定当前的内存区域,而后才可以进行处理,这样一来在高并发的环境下就是严重的影响到性能。

        而CAS采用的是一种悲观锁机制,其最大的操作特点是不进行强制性的同步处理,而为了保证数据修改的正确性,添加了一些比较的数据(例如:compareAndSet() 在修改之前需要进行数据的比较),采用的是一种冲突重试的处理机制,这样可以有效的避免线程阻塞问题的出现。在并发竞争不是很激烈的情况下,可以获得较好的处理性能,在JDK1.9 后为了进一步CAS 的操作性能又追加了硬件处理指令集的支持,可以充分的发挥服务器硬件配置的优势,得到更好的性能处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倪家李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值