并发笔记四:锁机制(二)

目录

并发笔记一:什么是线程不安全?
并发笔记二:线程中断机制
并发笔记三:线程的生命周期
并发笔记四:锁机制(一)
并发笔记四:锁机制(二)

1.关键字 volatile

  被volatile修饰的变量都是易变的、不稳定的。所以访问volatile不存在缓存等优化机制,每次访问都是直接从内存地址去读取值。
  虽说每次读取volatile变量都是从内存地址中读取,但是对于修改完的volatile变量要马上将其刷新到主内存中,也就是volatile提供了内存可见性,而对于原子性,volatile对于单个的读写具有原子性,而对于多个volatile或者是类似于 i++ 这种复合操作则不具备原子性。

  对于volatile修饰的变量进行读操作时的内存语义:
图片来自深入理解java内存模型

线程B进行读操作时,JMM会将线程B的本地内存置为无效,直接从主内存中读取变量。
实际上,线程A写共享变量时,会向所有读该变量的线程(线程B)发送消息,线程B接受到消息后开始从主内存中读取共享变量,这个过程实际上是线程A通过主内存想线程B发送消息。
  由于volatile关键字内存可见性的机制,所以volatile最适合那些多个线程读,很少的线程写的操作中。

2. Atomic

  Atomci是线程安全的升级版volatile的原子操作,多了一批原子操作,主要用于高并发下的高效程序处理。

  • 基本类:AtomicInteger、AtomicLong、AtomicBoolean。
  • 引用类型:AtomciReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference。
  • 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。
  • 属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。

2.1 AtomicInteger

 AtomicInteger atomicInteger = new AtomicInteger(0);
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.addAndGet(5));
        System.out.println(atomicInteger.getAndAdd(5));
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.getAndDecrement());
        System.out.println(atomicInteger.getAndSet(2));
        System.out.println(atomicInteger.doubleValue());

结果:

0
5
5
10
11
10
2.0

  进入到incrementAndGet()方法,可以看到

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

  我用的jdk12,这个方法的 U 代表的是Unsafe类,进入该方法,看到如下源码

	@HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

  其中weakCompareAndSetInt(o, offset, v, v + delta)方法最终会进入到native修饰的compareAndSetInt方法,说明它调用的是底层方法.
  简单介绍下这个方法,其中 Object o 就是我们传过去的对象,就是我们上面例子中的atomicInteger,long offset是当前值,比如执行2+1这个操作,offset就是2,delta就是1. int v代表的是当前从底层传过来的参数,weakCompareAndSetInt方法的作用就是,当我们传过来的值如果和底层传过来的值相同的话,就将当前值变为后面的v+delta这个值.
  这么做的理由是,如果当前我传入的值是2的话,那么从底层传过来的值也应该为2,但是多线程环境下,可能会导致其它线程修改了这个值,所以这个方法就是循环判断我传入的期望值和底层值是否相等,只有相等了才会进行后续操作.如果不一样的话就会一直判断.

2.1 AtomicLong

  AtomicLong的操作基本上和AtomicInteger相同,之所以将它提出来,是因为jdk8新增了一个比AtomicLong处理并发时效率更高的类–LongAdder.
  前面说到Atomic保持原子性的方法是底层用了CAS(CompareAndSetInt)方法,当调用的数值和底层传的数值不相同时会重复的进行判断,如果是高并发环境下,很容易造成大量重复性判断而浪费性能.
  为了解决这个问题,LongAdder诞生了,LongAdder的原理是将原先的一个变量,分解成多个变量供多线程同时操作.它自身内部维护了一个cells数组,当我们的数据value传过来是,LongAdder会将value分解成多个cell,每个cell单独维护自己的值,最后将所有的cell累加,便是最终的结果.
  举个例子:

  军训的时候教官想知道有多少学生,于是让学生站成一排进行报数,假设有100个学生,每个学生报数需要1s,如果从第一个同学开始报数一直到最后一个,那么就需要100S,期间如果有人报错,还要去确认上一个同学报的是什么数,然后继续报数(CAS算法).但是如果教官将这100个学生分成5个小团体(5个cell),每个团体单独报数同时进行,最后将每个团体报的总数加起来,就是总数了.

  由于Cells数组占用内存会比较打,所以当并发数并不高的情况下无需创建,而是直接操作Base变量,所以当我们并发数比较小的时候,使用AtomicLong,当并发数高的时候,使用LongAdder更佳.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值