count++只能加锁解决吗?Atomic可以吗?

例子
public class AtomicMain {

    public static void main(String[] args) throws InterruptedException {

        ExecutorService service = Executors.newCachedThreadPool();

        Count count = new Count();
        // 100个线程对共享变量进行加1
        for (int i = 0; i < 100; i++) {
            service.execute(() -> count.increase());
        }

        // 等待上述的线程执行完
        service.shutdown();
        service.awaitTermination(1, TimeUnit.DAYS);


        System.out.println(count.getCount());
    }

}

class Count{

    // 共享变量
    private Integer count = 0;
    public Integer getCount() {
        return count;
    }
    public  void increase() {
        count++;
    }
}

这个是个典型的count++问题。也就是count++操作不是原子操作,导致最后结果不是我们想象中的100。
因为count++需要经过读取-修改-写入三个步骤。举个例子:

  • 如果某一个时刻:线程A读到count的值是10,线程B读到count的值也是10

  • 线程A对count++,此时count的值为11

  • 线程B对count++,此时count的值也是11(因为线程B读到的count是10)

所以导致最后的结果不符合我们的预估结果。

怎么解决?

可以用用synchronized吗?可以,但是没必要。虽然synchronized已经经过优化,但是毕竟是加锁操作

  • Synchronized锁是独占的,意味着如果有别的线程在执行,当前线程只能是等待!

这个时候我们可以选择Atomic这个小备胎。

java.util.concurrent.atomic
基本类型:
  • AtomicBoolean:布尔型

  • AtomicInteger:整型

  • AtomicLong:长整型

数组:
  • AtomicIntegerArray:数组里的整型

  • AtomicLongArray:数组里的长整型

  • AtomicReferenceArray:数组里的引用类型

引用类型:
  • AtomicReference:引用类型

  • AtomicStampedReference:带有版本号的引用类型

  • AtomicMarkableReference:带有标记位的引用类型

对象的属性:
  • AtomicIntegerFieldUpdater:对象的属性是整型

  • AtomicLongFieldUpdater:对象的属性是长整型

  • AtomicReferenceFieldUpdater:对象的属性是引用类型

JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder
  • 是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。

Atomic包里的类基本都是使用Unsafe实现的包装类。

Unsafe里边有几个我们喜欢的方法(CAS):

// 第一和第二个参数代表对象的实例以及地址,第三个参数代表期望值,第四个参数代表更新值
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

从原理上概述就是:Atomic包的类的实现绝大调用Unsafe的方法,而Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出一条CPU指令cmpxchg,完成操作。这也就为啥CAS是原子性的,因为它是一条CPU指令,不会被打断。

使用Atomic解决count++
class Count{

    // 共享变量(使用AtomicInteger来替代Synchronized锁)
    private AtomicInteger count = new AtomicInteger(0);

    public Integer getCount() {
        return count.get();
    }
    public void increase() {
        count.incrementAndGet();
    }
}


// Main方法还是如上

修改完,无论执行多少次,我们的结果永远是100!

原理

CAS。面试的时候,对这个概念应该很熟知,我之前也经常被问到。

比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。

详细的CAS原理大家可以自行百度

参考:https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484416&idx=1&sn=540c0714263f8ee8b80ba90535162657&chksm=ebd74501dca0cc179e66c34cf3fa647f18860c670b47b0612fac0cb2c26b6cb17ad6824f0808&token=465096859&lang=zh_CN###rd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值